<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[WW Tech Blog - Medium]]></title>
        <description><![CDATA[WW inspires healthy habits for real life, using cutting edge science and technology to meet our community where they are and own scalable behavior change. - Medium]]></description>
        <link>https://medium.com/ww-tech-blog?source=rss----dfc4c7b378f1---4</link>
        <image>
            <url>https://cdn-images-1.medium.com/proxy/1*TGH72Nnw24QL3iV9IOm4VA.png</url>
            <title>WW Tech Blog - Medium</title>
            <link>https://medium.com/ww-tech-blog?source=rss----dfc4c7b378f1---4</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sun, 15 Nov 2026 16:02:44 GMT</lastBuildDate>
        <atom:link href="https://medium.com/feed/ww-tech-blog" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com/" rel="hub"/>
        <item>
            <title><![CDATA[Data Science Capstone Projects]]></title>
            <link>https://medium.com/ww-tech-blog/data-science-capstone-projects-8273a437c8db?source=rss----dfc4c7b378f1---4</link>
            <guid isPermaLink="false">https://medium.com/p/8273a437c8db</guid>
            <dc:creator><![CDATA[Betsey Corey]]></dc:creator>
            <pubDate>Mon, 15 May 2023 18:03:42 GMT</pubDate>
            <atom:updated>2023-05-15T18:05:05.682Z</atom:updated>
            <content:encoded><![CDATA[<p>This spring semester the Data Science team at WeightWatchers welcomed back a group of Business Analytics Masters students from Columbia University to participate in two capstone projects. WW proposed two topics, Voice Controlled Food Tracking, and Food Image Generation, in return eight students selected WeightWatchers and were split between the two topics to research and ultimately present their findings back to our organization at the end of their spring semester.</p><p>The project entailed weekly meetings between the students and the Data Science project lead and mentor to ensure guidance around project parameters and to create a sense of community and connectivity throughout. With a visit to our headquarters to attend one of our global town hall meetings and feel a sense of community, the community that we were founded on, 60 years ago!</p><p>At the conclusion of the project, the students shared back their findings internally with our tech and product organization and at our virtual hackathon. The article below highlights one of the team’s research around Image Generation with Stable Diffusion. Thank you to the students for their research and efforts, we enjoyed having you and look forward to welcoming future capstone students from Columbia again. Please read on to hear more about the project in their own words.</p><p><em>Betsey Corey, Director, Strategic Talent Programs &amp; Partnerships</em></p><h3><strong>Image Generation with Stable Diffusion</strong></h3><p>One of our favorite parts of the WeightWatchers app is the recipe recommendation section, where one can browse 12,000+ delicious (and healthy) meals. Each recipe is accompanied by beautiful, high-resolution images, which are often professionally created by WW. We tried to generate such images through <a href="https://huggingface.co/blog/stable_diffusion">latent diffusion models</a> by simply giving the model a prompt, for example, “pasta puttanesca”. The recent release and rapid progression of diffusion models inspired this project and we sought to use stable-diffusion-v2–1 to generate pictures that resembled professional images as closely as possible.</p><p><strong>Implementing the model &amp; initial results</strong></p><p>Getting things set up and generating the first images is very straightforward. We found running the model on a local or cloud GPU to speed things up considerably, especially when generating many images. At its most basic implementation, stable-diffusion-v2–1 only needs a prompt with a random seed being generated every time the model is run.</p><p>When experimenting with different recipes and food items, the model generated pictures of widely differing quality. Some prompts, such as full meal names (e.g., “pasta puttanesca with basil, black olives and tomatoes”) worked considerably better than single foods (e.g., “an apple”). Further, since most models generate a random seed each time the model is run, the same prompt will lead to vastly different outputs. This makes a rigorous evaluation more challenging. Here are some initial examples of images we generated:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1022/1*2dbAx3L_iQbdXSWV1giJJw.png" /></figure><p>(results for “clean pink table with a white round plate of chopped salad with sweet red pepper garlic clove […] string beans in the middle”)</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*2l01yTP2sPY35G98SLwoFA.png" /></figure><p>(results for “apple”)</p><p>While these results are not terrible, they are still far from the professional benchmark we are comparing them to. The most noticeable deficiency at this stage was the appearance of unwanted items, such as hands, fingers, or cutlery, appearing on pictures despite the fact that our prompts did not include them. This led us to explore options to constrain the model or further specify instructions.</p><p><strong>Improving the output</strong></p><p>After some research and quite a bit of trial and error, we implemented the following measures to improve the outputs. First, we fed a list of negative prompts to the model, i.e. items that should be excluded from appearing. In addition, we varied the degree of complexity or detail of the prompts. Simply prompting with “Apple” yields different results than “high-resolution, shiny, centered Apple”. We tried to systematically determine whether complexity and detail in the prompt improved the images. We varied the seed and complexity of prompts for a given food item and created numerous images. Below, results for “banana” and “olive oil” are shown. Note that the complexity scale is arbitrary and subjective. Low complexity is something like “banana” while level 5 complexity is more akin to: “DSLR food photograph of normally shaped croissant, in a circular blue plate, white napkin background, light coming from the side, natural lighting, masterpiece, 4K, 85mm f1.8”.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*kfM_-qpHET9rnqd4sPtktA.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*mzm9QOSS3luRV-0QtvhVsw.png" /></figure><p>This analysis showed that depending on the food, differing complexity levels of the prompts yielded different image qualities. To generate a realistic Apple or Banana, giving details such as “DSLR food photograph” and “high-resolution” proved to be effective. For items such as olive oil or salt, simplicity in the prompt was key.</p><p>The last avenue we have so far pursued is to use <a href="https://github.com/lllyasviel/ControlNet/blob/main/README.md">ControlNet</a> which allows one to input a sketch and prompt, based on which the model will output an SD-generated image. This allows us to specifically draw out the position and shape of foods, which helps to constrain the outputs. See below for an example:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*C4Mi-BFw3e2sLuD-oBw98Q.png" /></figure><p>Looking forward, we seek to test some further ideas for improved prompt quality, such as using models like <a href="https://huggingface.co/docs/transformers/model_doc/blip">BLIP</a>, which generate a prompt given an image. By feeding very high-quality images to such a model, we might be able to gain insight into how to phrase the prompts better.</p><p><strong>Lessons learned</strong></p><p>While the images we are currently generating are still not quite where we want them to be, the work with stable diffusion has taught us some useful lessons:</p><ul><li>When coming up with prompts, no one size fits all. Depending on the food to be generated, the prompt needs to be customized in order to get the best results.</li><li>Working with negative prompts makes life considerably easier! Especially when seeking to eliminate elements that keep on popping up.</li><li>Things move incredibly fast and each day new models are being published and resources made available. Staying up to date on Reddit, Discord and Huggingface has proven incredibly helpful.</li></ul><p>For inspiration, here are a few of the most recent images we generated:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/512/1*j4SSMIkT7JlyoSacd-JcWQ.jpeg" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/512/1*h_ywtCkAikVne_HwdJgtBA.jpeg" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/512/1*bSvd969FeF195fumoCeAtA.jpeg" /></figure><h3><strong>Voice Controlled Food Tracking</strong></h3><p><strong>Background</strong></p><p>Developing a food-tracking habit is consistently shown to be the most important factor in weight loss. WW is constantly looking for ways to make food tracking easier and exploring the use of AI tools to create better food tracking experiences. In this capstone project, our team built a website where people can talk about the food they eat, and the website can extract the food entities from the audio, matching with the current WW database, and returning the points.</p><p>The current way to track food on the Weight Watcher app is to manually search for the food that you eat for every meal. However, this process could become time-consuming and inefficient. For instance, Sweetgreen is one popular option among current customers. However, when it comes to a customized bowl, it could easily contain more than 10 different ingredients. This kind of complex real-life scenario deters users from tracking food consistently and accurately when they need it most. Taking images of food is a standard industrial solution and also might be your first thought. However, images could encounter problems as well. A human or model may be able to identify a casserole from an image, but this does not provide enough information to determine how we should track its nutritional value, which can vary widely depending on the ingredients used.</p><p>On the other hand, talking is a low-friction activity. People love to talk about food and can describe a whole meal in a few seconds. We believe that voice is the lowest friction method to track food. We also believe that the latest advances in open source and multimodal AI models have put “voice-to-track” within reach. In the rest of the blog, we will introduce it to you in more detail.</p><p><strong>App Structure</strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/980/1*onOC1cITqzGc_XYX-lR4Qw.png" /></figure><p>Let’s take a closer look at the app structure and tech stack. At a very high level, the app is mainly coded in Python. We utilized FastAPI, used state-of-the-art models from open source communities, which are mainly OpenAI and Hugging Face, and deployed with docker. For the front end, we used Streamlit, which is a free and open-source framework to rapidly build and share beautiful data science web apps.</p><p>To transcribe a user’s audio describing the food to text, we used the Whisper model from OpenAI. To extract food names from the transcribed text, we tried both food-based BERT and GPT API. Next, we encoded the found food names in embeddings with SBert and then compared their similarity with encoded food names in the WW database using FAISS. Finally, we return the most related food names in the database to the front end.</p><p><strong>App Demo</strong></p><p>The figure below is a screenshot of the user interface of our app. First, users would be asked to record their voice by clicking on the microphone icon. Once the recording is successful, users would click on the “Detect” button to initiate the audio transcription, food extraction and matching process. The final matchings along with their respective portions, units, and points would be returned to the user and the user would provide feedback on the accuracy of the results by either selecting “Yes!!” or “No, this is not what I ate!” to answer the question “Is this what you ate?”. If the latter is selected, the user could choose to manually type in what they ate in the text box and new food extractions as well as final matchings would be generated based on the input in the textbox. Given that the matching is correct, the user could then click on the drop down menu on the rightmost column to adjust the amount of food intake as well as the unit. In addition, we also allow users to choose OpenAI’s GPT 3.5 Turbo as an alternative for Food-Based BERT to perform food entity extraction. Users could switch to this option by selecting the “OpenAI GPT 3.5-turbo” button and inputting their OpenAI API key in the leftmost column. Overall, our clear and interactive user interface allows users to track their daily food intake in an efficient way and provides highly customized results.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*YPnmyCBoGK9bjQ-VyKs_Hg.png" /><figcaption>App User Interface</figcaption></figure><p><strong>User Expression Pattern Research</strong></p><p>Having our model at hand, we also conducted research on the user side to gain a deeper understanding of how people would use our app. We collected 112 recordings of people describing what they ate on the Columbia University morningside campus and performed analysis on these data collected in an attempt to find patterns in user expressions. The insights we gained from these real-life user data are as follows:</p><ul><li>Insight 1: Users almost always use ‘a’ to specify a single item but they tend to use ‘some’ when they had more than one item rather than specifying the specific amount</li><li>Insight 2: When people describe what they ate, the name of the restaurant/ store is often neglected</li><li>Insight 3: “and” usually connects two food entities whereas “with” might connect two food entities (e.g. toast with tea) or might be used when the user wanted to provide a more detailed description of the ingredients inside a food (e.g. coffee with oat milk/ bagel with cream cheese)</li><li>Insight 4: Nouns (mostly foods) occur most frequently in people’s descriptions, with an overall frequency of 0.24. Better prompts might reduce the percentage of redundant information such as adjectives and adverbs.</li></ul><p><strong>Results</strong></p><p>The accuracy of food entity extraction was compared between OpenAI GPT method and Food-Based BERT method. Out of all 112 collected transcribed texts, the Food-Based BERT method correctly extracts all food entities from 56.8% of the sentences, while the GPT method’s performance is 81.1%. Across all sentences, the Food-Based BERT method correctly detects 76.6% of entities, compared to 93.4% using the GPT method.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*hWDWITQgZVE6DYAieBprQQ.png" /><figcaption><em>Comparison of NER accuracy</em></figcaption></figure><p><strong>Next Steps</strong></p><p>Potential future improvements that could be made to our app include running Whisper model on GPU to increase speed, finding a better name entity recognition model in terms of accuracy and speed, and adding aesthetic elements to our user interface. The next step would be to integrate our model with the WW app to provide users with this time-saving alternative for food tracking.</p><p><strong>Interested in joining the WW team? </strong>Check out the <a href="https://www.weightwatchers.com/us/corporate-careers">careers page</a> to view technology job listings as well as open positions for other teams.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=8273a437c8db" width="1" height="1" alt=""><hr><p><a href="https://medium.com/ww-tech-blog/data-science-capstone-projects-8273a437c8db">Data Science Capstone Projects</a> was originally published in <a href="https://medium.com/ww-tech-blog">WW Tech Blog</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Combining nutritionist expertise and ML to provide high-quality nutrition data worldwide]]></title>
            <link>https://medium.com/ww-tech-blog/combining-nutritionist-expertise-and-ml-to-provide-high-quality-nutrition-data-worldwide-dc193ff0b395?source=rss----dfc4c7b378f1---4</link>
            <guid isPermaLink="false">https://medium.com/p/dc193ff0b395</guid>
            <category><![CDATA[nutrition]]></category>
            <category><![CDATA[machine-learning]]></category>
            <dc:creator><![CDATA[Yameng Zhang]]></dc:creator>
            <pubDate>Thu, 20 Apr 2023 20:27:56 GMT</pubDate>
            <atom:updated>2023-04-24T13:23:30.217Z</atom:updated>
            <content:encoded><![CDATA[<p>Authors: <a href="https://medium.com/u/6091934d4b0d">Reka Daniel-Weiner</a>, <a href="https://medium.com/u/8d39d46702ad">Yameng Zhang</a></p><p>When you embark on a journey to improve your health and wellness, making informed choices on which foods you consume to fuel your body is crucial. At WW, our behavior and nutrition science teams conduct clinical trials <a href="https://www.weightwatchers.com/us/science-center/peer-reviews">[ref]</a> and constantly monitor the latest scientific literature to provide our members with the most current information on which nutrients and overall dietary pattern will maximize health benefits. For example, we know that consuming fiber is beneficial for heart health while protein provides the body with important building blocks. On the other hand, saturated fats are not beneficial for heart health, and consuming sugar, especially added sugar, can increase the risk of both obesity and diabetes <a href="https://www.dietaryguidelines.gov/sites/default/files/2020-12/Dietary_Guidelines_for_Americans_2020-2025.pdf">[ref]</a>. We take the complexity of all of this nutritional information and summarize it into a single value (known as Points®) to behaviorally nudge our members to an overall healthier dietary pattern using the most cutting-edge nutrition and behavior change science.</p><p>This is why we strive to have the most comprehensive and complete food database in the world. However, with hundreds of millions of foods from around the globe, this is a large challenge for our nutritionist team to handle manually. Therefore they are in constant and close collaboration with us, the Data Science and Machine Learning Engineering team at WW, to fill in the blanks and provide them with precise estimates of missing values. One of the contact points where this need becomes especially apparent is when our members use the points calculator in our app. There they can input the nutrition information listed on a food label to instantly receive the Points® value for any food that they consider consuming. Unfortunately, many countries do not require all information that has been identified by scientific research to be important to estimate the impact of a food on health and wellness to be reported on the label. For example, the US only started requiring listing added sugar content in 2020/2021 [<a href="https://www.accessdata.fda.gov/scripts/cdrh/cfdocs/cfcfr/CFRSearch.cfm?fr=101.9&amp;SearchTerm=nutrition%20label">ref</a>]. Still, in many other countries currently added sugar is not required to be listed on the label, despite its known detrimental effects on health.</p><p>In this blog post, we discuss how the Data Science and Machine Learning Engineering team at WW built an ML model to estimate the added sugar content of a food, and how we made this information available to be consumed by the calculator within the WW app as well as our internal database. As a first step, we worked together with our nutrition science experts to review currently available methods of added sugar estimation. One of the most used approaches is based on a manual process <a href="https://www.nature.com/articles/ejcn2014256">[ref]</a>, which is very valuable as an internal tool for our nutritionist team when they review existing entries in the database. However, this manual process would not have been suitable for use within our member-facing points calculator considering the subjective evaluations involved. After in-depth discussions with the nutrition science team, we came to the conclusion that the problem could be approached as a machine learning problem. After trying out alternative approaches such as linear regression and KNN we decided to go ahead with building an XGBoost <a href="https://xgboost.readthedocs.io/en/stable/">[ref]</a> model, a method which has shown great success discovering nonlinear relationships in tabular data. In addition to the high accuracy of the resulting model, due to its scalability XGBoost also seemed like an ideal choice of model as we expected the final resulting endpoint to be able to handle individual requests from the calculator with fast response times, as well as being able to respond to batch requests for backfilling suggested values to be reviewed in the food database.</p><p>As a first iteration, we went ahead and collected all food data available in the US which already had entries for added sugar values. Using total sugar (i.e. all sources of sugar, including naturally occurring and added sugars), carbohydrates, fiber, total fat, sodium, calories, saturated fat as well as food category as features, we built an XGBoost model on 80% of the available data and performed hyperparameter optimization on it. During this initial exploration phase, we also learned that training on all foods, including the ones with no sugar present (where manual estimation of added sugar would automatically default to zero) yielded a more reliable model, presumably because it allowed XGBoost to learn relationships among all nutrients present. Using the resulting model to predict added sugar values on the 20% hold-out dataset, we saw very encouraging results, which we published in a scientific article <a href="https://www.mdpi.com/2072-6643/15/4/803">[ref]</a>. However, we did not think a model validated on only US foods would deliver sufficiently reliable results to our members, especially globally.</p><p>As a next step, we asked our global team of nutritionists to evaluate our model predictions on manually estimated added sugar values on international data (as due to the lack of added sugar information no ground-truth dataset is available). Unsurprisingly, the manual process identified that for many foods from our international food databases, the added sugar predictions were suboptimal, and that there were specific food categories in which this was the case, for example for sugary drinks and milk products. Our nutrition science team focussed on the areas where the model was most wrong, and in several iterations, we added newly verified international foods to our training and test datasets. As the model will mostly be used in markets that do not have added sugar on the label, i.e. outside the US, we made sure that at least 30% of the dataset consisted of non-US foods. We also learned that adding the food category as a feature increased model accuracy only marginally, so we decided to remove it from the model in order to increase usability and decrease guesswork for the members when using the calculator.</p><p>In order to gain a more intuitive understanding of how the XGBoost model uses feature values and their interactions to make predictions, we also looked at SHAP values, a measure of feature importance based on game theory <a href="https://www.nature.com/articles/s42256-019-0138-9">[ref]</a>. The figures below provide an example of SHAP plots; each dot represents one specific food, the horizontal axis shows the value of a feature (total sugar or fiber), and the vertical axis represents which direction the value of the feature changed the added sugar prediction for each food. The top figure shows that on average the model predicts higher added sugar values for a food with higher total sugar. Intuitively this makes sense — the more sugar a food contains, the more we expect it to contain added sugar. The bottom figure adds some nuance to this observation: here, each dot is colored by its sugar value, i.e. foods high in sugar are shown in red, while foods low in sugar are shown in blue, and the value of fiber is shown on the horizontal axis. For foods that are high in sugar, but also high in fiber, the model predicts lower values of added sugar. This makes sense as foods naturally high in fiber, such as fruits, tend to have little to no added sugar. Considering such summary plots provides transparency into how the model derives predictions. This understanding can then serve as a basis for discussions with our nutrition science team to pinpoint areas where the model is underutilizing specific information and should be retrained with better, more specific, or simply more data.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/904/0*Ot2N5L5m6sC1mG31" /></figure><p>After several iterations, we were able to achieve an average mean absolute error of 0.8g (corresponding to 3.2 calories) at the default portion size on global foods. As there are an average of 180 calories in a portion, and the USDA suggests consuming not more than 10% of calories from sugar <a href="https://www.dietaryguidelines.gov/sites/default/files/2020-12/Dietary_Guidelines_for_Americans_2020-2025.pdf">[ref]</a>, we felt that this was a reasonable starting point for a first launch into production, with the prospect of iterating on the model as more data becomes available.</p><p>At WW we believe that it is crucial that the Data Science and Machine Learning Engineering team itself is responsible for deploying models <a href="https://medium.com/ww-tech-blog/well-do-it-live-updating-machine-learning-models-on-flask-uwsgi-with-no-downtime-9de8b5ffdff8">[ref]</a> so iterations can be done seamlessly. To expose the model we set up an endpoint in FastAPI <a href="https://fastapi.tiangolo.com/">[ref]</a>, which is a Python Framework and set of tools that enables developers to use a REST interface to call commonly used functions to implement applications. By sending requests containing necessary ingredient information to the endpoint, the estimated added sugar result will be included in the response to be consumed both by the calculator to provide live initial estimates, and by our database to prefill an estimated added sugar field for review by the nutrition science team. Below you see the calculator as it appears in the US (where added sugar is available on the labels; “Added sugar” is a field required to be filled out by the member) and in the UK (where added sugar needs to be estimated; no “Added sugar” field available):</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*GWatMOIHp03mIHRLTg5AEA.png" /><figcaption>Calculator for US (left) and UK (right)</figcaption></figure><p>Given the global application of the added sugar model, the endpoint is able to handle requests that are slightly different among different markets and apply corresponding logic. For example, in the US fiber is included under total carbohydrates, while in the UK it is not; the endpoint automatically takes this into account when it interprets the incoming information from the nutrition label. Additionally, while it is often voluntarily disclosed by manufacturers, fiber information is not required on the label in the UK. If this optional information is missing, the endpoint will first apply a fiber estimation model, which we reviewed both independently and in conjunction with the added sugar model, to get the best estimation of the field and then pass the value to the added sugar estimation model.</p><p>This endpoint has been used to batch-backfill all foods in our current database with estimations to provide guidance for manual review and is being currently hit about 100 thousand times a day, providing our members with the information they need to make informed decisions on which foods they consume to support their health and wellness journey.</p><p><strong>Interested in joining the WW team? </strong>Check out the <a href="https://www.weightwatchers.com/us/corporate-careers">careers page</a> to view technology job listings as well as open positions for other teams.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=dc193ff0b395" width="1" height="1" alt=""><hr><p><a href="https://medium.com/ww-tech-blog/combining-nutritionist-expertise-and-ml-to-provide-high-quality-nutrition-data-worldwide-dc193ff0b395">Combining nutritionist expertise and ML to provide high-quality nutrition data worldwide</a> was originally published in <a href="https://medium.com/ww-tech-blog">WW Tech Blog</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Using X12 EDI with Red Hat Fuse/Apache Camel Apps]]></title>
            <link>https://medium.com/ww-tech-blog/using-x12-edi-with-red-hat-fuse-apache-camel-apps-1b6536507d27?source=rss----dfc4c7b378f1---4</link>
            <guid isPermaLink="false">https://medium.com/p/1b6536507d27</guid>
            <dc:creator><![CDATA[Christopher Scalise]]></dc:creator>
            <pubDate>Tue, 21 Mar 2023 21:42:05 GMT</pubDate>
            <atom:updated>2023-03-21T21:42:05.498Z</atom:updated>
            <content:encoded><![CDATA[<p>A few years ago, we started working with a vendor that uses X12 EDI for sharing data. Although I have been in the IT field for many years, this was my first experience with EDI. For anyone new to X12 and EDI, here is a brief description that I found online:</p><p><em>X12 is a message formatting standard used for Electronic Data Interchange (EDI) documents. It’s used by trading partners to share business documents in an agreed-upon and standard format. X12 is the most common EDI standard used in the United States.</em></p><p>Our vendor was using EDI to send payment details to WW via an 835 file. A brief description of the 835 transaction is below:</p><p><em>The Electronic Remittance Advice (ERA), or 835, is the electronic transaction that provides claim payment information. These files are used by practices, facilities, and billing companies to auto-post claim payments into their systems.</em></p><p>The message flow was unidirectional — from the vendor to WW and only the 835 transaction was being sent to us.</p><p>Research was done to find 3rd party software that we could leverage to easily communicate using EDI. We decided to use the Transformer software provided by Trace Financials as it provided flexibility for parsing EDI 835 transactions as well as dozens of other EDI transactions.</p><p>Via the Transformer tool, we were able to parse the 835 transaction, and via scripting provided by the tool, extract the required fields from the transaction that were needed for processing. The Transformer tool enables you to create a service and export a jar file with callable APIs that can be used by an application.</p><p>Below is a code fragment that shows the service API being called from a route in Java code. Some items were removed or generalized for privacy reasons.</p><p>// Process 835 file to extract information and store it in the database<br>from(“direct-vm://process835”)<br>.routeId(“process835”).routeDescription(“process835”)<br>.pollEnrich().simple( /* file details were removed */ )<br>.convertBodyTo(String.class)<br>.log(“calling txfrmr”)<br><strong>.to(“txfrmr://ServiceName/APIName”)<br></strong>.log(“txfrmr called — ${body.size} records”)<br>.split(body()).unmarshal()<br>.jacksonxml()<br>.to(“direct-vm://insertDataToDatabase”);</p><p>We configured the Transformer tool to parse the 835 transaction and extract the data fields of interest. Via the tool, we generated a jar file with an API endpoint for the data mapping and used this jar with our deployed application. When an 835 file is sent from the vendor, our route calls the exposed API which parses the transaction and returns the data which we split, unmarshal, and process.</p><p>This past year, we started working with a new vendor that also uses EDI transactions. The interface for this vendor is more complex. The message flow is bidirectional and uses multiple EDI transaction types including acknowledgment transactions.</p><p>A high-level description of the transaction flow is given below:</p><ul><li>When an order is placed, WW constructs and sends an EDI 850 “purchase order” transaction to the vendor and they respond with an EDI 997 “acknowledgment” with a status indicating if it was successfully processed.</li><li>When the vendor ships the item, they send an EDI 856 “advance shipping notification” transaction to WW and we process it and return an EDI 997 “acknowledgment”.</li><li>At some point, the vendor sends an EDI 810 “invoice” transaction to WW and we process it and return an EDI 997 “acknowledgment”.</li></ul><p>The Transformer tool was configured to parse the different EDI transactions. We created a jar with multiple API endpoints (one for each transaction’s data mapping) and used this jar with our deployed application.</p><p>In addition to the added complexity of using bidirectional EDI transactions along with acknowledgment transactions, we needed to generate a unique sequence number for each outgoing transaction as this was required by our vendor. Since our application uses a database to keep track of purchase orders, shipment details, invoice information, and other data — we used the database to generate a unique sequence number for any outgoing transactions.</p><p>Due to the sensitive nature of some of the data being sent between WW and our vendor, we are encrypting EDI message files sent to the vendor and the vendor is encrypting EDI message files sent to WW.</p><p>I hope I gave you a high-level idea of how we incorporated X12 EDI transaction processing into a few of our apps at WW. For details on X12, please visit x12.org.</p><p><strong>Interested in joining the WW team? </strong>Check out the <a href="https://www.weightwatchers.com/us/corporate-careers">careers page</a> to view technology job listings as well as open positions for other teams.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=1b6536507d27" width="1" height="1" alt=""><hr><p><a href="https://medium.com/ww-tech-blog/using-x12-edi-with-red-hat-fuse-apache-camel-apps-1b6536507d27">Using X12 EDI with Red Hat Fuse/Apache Camel Apps</a> was originally published in <a href="https://medium.com/ww-tech-blog">WW Tech Blog</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[The choice to be disconnected]]></title>
            <link>https://medium.com/ww-tech-blog/the-choice-to-be-disconnected-2c248dc55ccc?source=rss----dfc4c7b378f1---4</link>
            <guid isPermaLink="false">https://medium.com/p/2c248dc55ccc</guid>
            <category><![CDATA[tech]]></category>
            <category><![CDATA[accessibility]]></category>
            <dc:creator><![CDATA[Michael D'Anvers]]></dc:creator>
            <pubDate>Wed, 08 Mar 2023 14:43:57 GMT</pubDate>
            <atom:updated>2023-03-08T14:44:22.676Z</atom:updated>
            <content:encoded><![CDATA[<p>A call to action to build tech that works well offline and with slow internet speeds, or learning to stop worrying and meander through the green belt.</p><p>After a multi-leg train ride from Heathrow Airport, I was somewhere in the southwest of England waiting for a good friend to pick me up. I mistakenly took a pricier express train and was 45 minutes early, hoping Kristina had gotten my text. I hadn’t heard from her all morning, but I lucked out. I learned by chance she stepped out to check her text messages at a specific spot on the street. Kristina, her wife, Sarah, and her terrier pup, Lila, moved to this speck on the map for many reasons, one being a reset from city and work life after the pandemic.</p><p>I listened to the click-clack of horse hooves on cobblestones while watching some teenagers aboard a tractor drinking as one of them drove to the pub. It felt so otherworldly, light-years from our dual-monitor home offices in a fourth floor walk-up with a video doorbell monitoring the surrounding area or to the local Starbucks where our smartphones talk to smart cash registers to redeem a free coffee. In cities and suburbia, tech is inescapable and pervasive. Does rural life — so distant from ours — need access to technology as someone living in the big city?</p><p>Kristina yelled at Lila, “Don’t eat sheep poop!” The dog responded by pulling harder on the leash. The four of us strolled along a public path that traversed through a farm, woods, and grazing land, avoiding stinging nettles as we passed thigh-high grass, hedgerows, sheep, scampering quail, cows, the occasional human, and wild pheasants struggling to keep their distance from the terrier. We made our way to the top of a hill overlooking the town of Wiveliscombe (pronounced Wiv-el-is-cum) with a population south of 3,000. Pondering life with one of my best friends, we paused to admire our surroundings. I slid my phone out of my pocket — noticing my pants were splattered with brick red mud — and snap, snap, snap, I digitized the vista, opening Instagram without thought to get some likes. I quickly realized I was barless! Kris turned to me and said, “No service out here.” Without a choice, I quickly adapted to the limited tether and enjoyed the rest of our nature walks without the likes.</p><p>The question still remains: Why disrupt such tranquility with better access to tech?</p><p>One of my guiding principles in accessibility is “everything should be accessible. Don’t limit access to impose morals.” Accessibility is about choice. The example I always use is the soda vending machine. Should the vending machine be accessible? Someone could argue soda is bad, why make it easier to drink? If that’s the case, do it through laws, not by imposing your morals through lack of accessibility.</p><p>The case to build tech that loads fast with slow internet, and functions offline is the same. Access to tech in rural areas can mean checking for directions while meandering in the woods, calling for help where no people are within earshot, or a farmer at the market completing a digital transaction. Access to technology is about choice, and making tech work in rural areas is one more facet of accessibility.</p><p>Even potential pitfalls, like the unending itch to get likes or doomscrolling should not be used as an excuse to deny access to the technology (it’s a whole other problem that could be attacked using more thoughtful design, but that’s a topic for another blog post.)</p><p>On a side note, cutting-edge solutions like Starlink (satellite internet) will help to bridge our worlds, but it won’t solve the problem of a lack of high-speed internet or tech that is usable offline.</p><p>Our company data shows that one of the most used features of the WeightWatchers® app is the barcode scanner, which was designed so members can quickly know the Points® value of a packaged food while at a grocery store. The barcode scanner relies on a database in the cloud, so if our app can’t send a request to the server, the barcode scanner is useless. What creative ways could solve this problem? Thinking out loud, one solution could be when the member is online, downloading the necessary data of foods in their area so the app isn’t reliant on a server when offline. The point being, solutions are reachable and quite doable.</p><p>That’s just one example of how the WW app can be improved when offline. A very high percentage of popular apps today have any number of downfalls when it comes to load times and offline abilities. Why is that?</p><p>It’s the same reason everything isn’t accessible. The two big reasons: (1) Lack of representation. (2) It’s human nature to design for yourself, who you know, and where you live. Many tech companies generally are in San Francisco and New York City — cities with high speed internet and cell service, where everyone is using the latest, greatest, and fastest technology. These two reasons create an unconscious bias where people overlook the value of designing for poor people, or for people with slow or no internet access.</p><p>But there are solutions to these problems. Start by adding to your definition of what diverse hiring practices look like, including looking at people from rural areas and a range of income brackets. Companies can also build low internet, no internet practices into your product lifecycle — from research to design to testing.</p><p>Connecting everyone to the internet means more than not getting lost in the woods, it means climate refugees can find resources and shelters, it means anyone who relies on assistive technology can continue to do so wherever they are, and, of course, it can mean anyone at any grocery store can make healthy food choices.</p><p>I really enjoyed being disconnected in rural England but I also enjoyed sending my parents back home pictures once I was on Wi-Fi. My personal takeaway is to take more walks disconnected, but my professional takeaway is that we have an obligation to connect everyone and to continue to make everything accessible.</p><p>I really enjoyed being disconnected in rural England but I also enjoyed sending my parents back home pictures once I was on Wi-Fi. My personal takeaway is to take more walks disconnected, but my professional takeaway is that we have an obligation to connect everyone and to continue to make everything accessible.</p><figure><img alt="Green rolling hills with a red farmhouse in the distance, sheep in the forground, and the sky is full of white fluffy clouds." src="https://cdn-images-1.medium.com/max/1024/1*9bNYiKMzpA6Dn-u9mqGQMA.jpeg" /></figure><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=2c248dc55ccc" width="1" height="1" alt=""><hr><p><a href="https://medium.com/ww-tech-blog/the-choice-to-be-disconnected-2c248dc55ccc">The choice to be disconnected</a> was originally published in <a href="https://medium.com/ww-tech-blog">WW Tech Blog</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Springboot/Java application observability with OpenTelemetry & NewRelic — Part 2]]></title>
            <link>https://medium.com/ww-tech-blog/springboot-java-application-observability-with-opentelemetry-newrelic-part-2-ebc5c24fc3b7?source=rss----dfc4c7b378f1---4</link>
            <guid isPermaLink="false">https://medium.com/p/ebc5c24fc3b7</guid>
            <category><![CDATA[spring-boot]]></category>
            <category><![CDATA[otel]]></category>
            <category><![CDATA[fluentd]]></category>
            <category><![CDATA[opentelemetry]]></category>
            <category><![CDATA[new-relic]]></category>
            <dc:creator><![CDATA[Amit Misra]]></dc:creator>
            <pubDate>Wed, 04 Jan 2023 22:42:02 GMT</pubDate>
            <atom:updated>2023-01-06T14:46:22.575Z</atom:updated>
            <content:encoded><![CDATA[<h3>Springboot/Java application observability with OpenTelemetry &amp; NewRelic — Part 2</h3><h3>Logs in JSON format</h3><p>In order to get the logs in JSON format, we need to let the Log4j2 know about the layout and we will use a template for our logs which will look like this:</p><h4>JsonTemplateLayout</h4><p>Create a new file under the src/main/resources folder by the name — Log4jEventLayout.json.</p><p>You can find additional details about the template layout <a href="https://logging.apache.org/log4j/2.x/manual/json-template-layout.html">here</a>.</p><p>Couple of things to note here:</p><ol><li>You would see we are duplicating trace_id and span_id by adding trace.id and span.id. This is intentional as NewRelic does not understand trace_id and span_ids.</li><li>The service.name is externalized and will be replaced by the value stored in the environment variable OTEL_SERVICE_NAME variable.</li></ol><h4>Letting Log4j2 know about this configuration</h4><p>This can be done as follows:</p><blockquote>&lt;?xml version=”1.0&quot; encoding=”UTF-8&quot;?&gt;</blockquote><blockquote>&lt;Configuration status=”WARN” monitorInterval=”30&quot;&gt;</blockquote><blockquote>&lt;Appenders&gt;</blockquote><blockquote>&lt;Console name=”ConsoleAppender” target=”SYSTEM_OUT” follow=”true”&gt;</blockquote><blockquote>&lt;JsonTemplateLayout eventTemplateUri=”classpath:Log4j2EventLayout.json”/&gt;</blockquote><blockquote>&lt;/Console&gt;</blockquote><blockquote>&lt;/Appenders&gt;</blockquote><blockquote>&lt;Loggers&gt;</blockquote><blockquote>&lt;Root level=”info”&gt;</blockquote><blockquote>&lt;AppenderRef ref=”ConsoleAppender” /&gt;</blockquote><blockquote>&lt;/Root&gt;</blockquote><blockquote>&lt;/Loggers&gt;</blockquote><blockquote>&lt;/Configuration&gt;</blockquote><p>At this point, all the logs spit out by our application should be in JSON format.</p><blockquote>{“timestamp”:”2022–08–19T12:55:33.485–0400&quot;,”thread.name”:”restartedMain”,”log.level”:”INFO”,”logger.name”:”com.example.otelservice.OtelServiceApplication”,”message”:”No active profile set, falling back to 1 default profile: \”default\””,”service.name”:”wcp-otel-service”}</blockquote><p>Some samples with the span and trace details.</p><blockquote>{“timestamp”:”2022–08–19T13:14:56.615–0400&quot;,”thread.name”:”http-nio-8080-exec-1&quot;,”log.level”:”INFO”,”logger.name”:”com.example.otelservice.service.GreetingService”,”message”:”greet service invoked”,”trace_id”:”f05a3a2dfcc8d04d4f18beaa11f17909&quot;,”span_id”:”88477fb780ebf427&quot;,”trace.id”:”f05a3a2dfcc8d04d4f18beaa11f17909&quot;,”span.id”:”88477fb780ebf427&quot;,”service.name”:”wcp-otel-service”}</blockquote><h3>OpenTelemetry instrumentation</h3><p>As per the <a href="https://opentelemetry.io/docs/instrumentation/java/automatic/">instructions</a> on the OpenTelemetry website, we need to add a java agent to your application. This is going to be a two-step process. First download and stage the instrument library at a known location, and then configure the spring boot maven plugin to use the java agent accordingly. This is accomplished using the maven-dependency-plugin. Refer to the pom.xml file for the project.</p><h3>Introduction</h3><p>Before diving into the implementation details, let’s take a look at the high-level architecture of the <a href="https://opentelemetry.io/docs/collector/">collector</a>. The following diagram is from their website:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*X__8HNaMtytXANnT" /></figure><p>The OpenTelementry collector is based on a pluggable model with three primary components as can be seen above. You have the</p><ol><li>Receiver</li><li>Processor</li><li>Exporter</li></ol><h3>Set up otel-collector</h3><p>For this demo, we are going to use the docker image provided by opentelemetry-collector-contrib. We will start off by creating two files.</p><ul><li>otel-config.yaml</li><li>docker-comopse.yaml</li></ul><p>Please refer to the <a href="https://github.com/amitmisra-ww/otel-service">github</a> project for references to these files.</p><h3>Bringing up the docker instances</h3><p>At this point, we are ready to take our application and OTel Collector for a spin. Before we bring up the Oel Collector, we need to export the NewRelic API key. Export your API key like so — export NEW_RELIC_API_KEY=&lt;YOUR_API_KEY&gt;</p><p>Now we can start the docker instances by firing up the following command: ./mvnw clean install &amp;&amp; docker compose up –build</p><p>The first part of the command outputs the uber-jar and places the opentelemetry-javaagent.jar file under target/agents directory. The second part of the command first builds the docker instance for our service and brings up the collector along with it.</p><p>Try to call our service a bunch of times using the following curl command — curl <a href="http://localhost:8080/hello%5C?name%5C=OpenTelemetry">http://localhost:8080/hello\?name\=OpenTelemetry</a></p><p>You should be able to see our new service under <strong>Service — OpenTelemetry</strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*4LpnesYKb6zhBpRE" /></figure><p>Head over to the “Transactions” from the left navigation menu and you should see something like this.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*YUlmCs38Ds20Ggof" /></figure><p>Click on one of the traces below you will be able to see one trace and a couple of spans.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*NASrspIS9s_IdFuV" /></figure><h3>Taking care of logs</h3><p>Now that we have trace flow working, it would be nice to see the logs adjacent to our traces. This will help triage any issue we are trying to resolve. To get the logs exported from our application to NewRelic we will take a little help from <a href="https://www.fluentd.org/">FluentD</a>. You can read more about FluentD and the underlying architecture <a href="https://www.fluentd.org/architecture">here</a>.</p><h3>Log flow</h3><p>At a high level, this is what the flow will look like.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*3lBRpaZRo8DfLg5k" /></figure><h3>Setup the Fluentd instance</h3><p>The first step is to configure the Fluentd instance to receive incoming logs on port 24224. After receiving these logs, we will want to filter all the logs in JSON format and send it over to our OpenTelemetry forwardreceiver listening on port 8006.</p><p>Please refer to the fluentd.conf and the fluentd docker service in the docker-compose.yaml file in the <a href="https://github.com/amitmisra-ww/otel-service">github</a> repo.</p><h3>Final Results</h3><p>Once we have established the Fluentd instance, two things should happen on the NewRelic portal. Under the wcp-otel-service, when you click on Logs in the left navigation menu, you should see your application logs.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*B-SYe7b7KSYbialI" /></figure><p>Click on Open in the logs to get a view.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*bMBYFnOJ4H4GGpTy" /></figure><p>This allows you to see all the application logs in one place, but we can do better than that. Earlier we said it will be really helpful to associate the traces and the logs. If we head over to the Transactions menu, you will see the usual transactions.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*VPp1hznZ4PNq8_Cz" /></figure><p>Now if you click on one of the traces you should see something like this.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*rpzim5pRijc-1mLl" /></figure><p>Interestingly, now the Logs aren’t showing 0, but 2 2. Heading over the logs you should see something like this.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*jwxoNbQn7QOaHIbp" /></figure><p>Now we have all traces and spans. This should help to debug issues easier than before.</p><h3>Next Steps</h3><p>In the subsequent post, I would like to cover the following topics:</p><ul><li>Logging exception traces appropriately</li><li>Exposing critical metrics and sending them to NewRelic via our OpenTelemetry Collector.</li></ul><p><strong>Interested in joining the WW team? </strong>Check out our <a href="https://www.weightwatchers.com/us/corporate-careers">careers page</a> to view technology job listings as well as open positions on other teams.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=ebc5c24fc3b7" width="1" height="1" alt=""><hr><p><a href="https://medium.com/ww-tech-blog/springboot-java-application-observability-with-opentelemetry-newrelic-part-2-ebc5c24fc3b7">Springboot/Java application observability with OpenTelemetry &amp; NewRelic — Part 2</a> was originally published in <a href="https://medium.com/ww-tech-blog">WW Tech Blog</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Springboot/Java application observability with OpenTelemetry & NewRelic — Part 1]]></title>
            <link>https://medium.com/ww-tech-blog/springboot-java-application-observability-with-opentelemetry-newrelic-part-1-1948cc819653?source=rss----dfc4c7b378f1---4</link>
            <guid isPermaLink="false">https://medium.com/p/1948cc819653</guid>
            <category><![CDATA[otel]]></category>
            <category><![CDATA[fluentd]]></category>
            <category><![CDATA[spring-boot]]></category>
            <category><![CDATA[opentelemetry]]></category>
            <category><![CDATA[new-relic]]></category>
            <dc:creator><![CDATA[Amit Misra]]></dc:creator>
            <pubDate>Wed, 04 Jan 2023 22:25:27 GMT</pubDate>
            <atom:updated>2023-01-04T22:25:27.804Z</atom:updated>
            <content:encoded><![CDATA[<h3>Springboot/Java application observability with OpenTelemetry &amp; NewRelic — Part 1</h3><p>Weaving OpenTelemetry, FluentD, and NewRelic for complete observability</p><h3>Observability: more is less</h3><p>Being able to track individual transactions and see application logs in one place is a requirement when we are dealing with a micro-services explosion. You can head over here to read more about <a href="https://opentelemetry.io/docs/concepts/observability-primer/">observability</a> and <a href="https://opentelemetry.io/docs/concepts/signals/traces/">distributed traces</a>. There are two parts to this puzzle though. We need the ability to generate traces and spans, usually achieved by instrumenting the method calls and tying application logs to these traces and spans.</p><p>We will walk through the entire process and, in the end, you will have a working example that would look something like this on NewRelic. The source code of the working example can be found <a href="https://github.com/amitmisra-ww/otel-service">here</a>.</p><h3>Trace and span view</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*OuJPkdrxKBzRIQOx" /></figure><h3>Application logs associated with those traces and spans</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*tI5dCw0mXgC-SCcq" /></figure><h3>Trace and span setup</h3><p>Setting up traces and spans in a Springboot application is relatively simple. We will use</p><ul><li>Free account setup for NewRelic.</li><li>Springboot application with Log4j2 logging in JSON format.</li><li>Wrap up the application with OpenTelemetry instrumentation.</li><li>Use Docker to bring-up OpenTelemetry Collector to talk to NewRelic.</li></ul><h3>NewRelic account setup</h3><p>If you already have an account then simply move to the next step. Otherwise, follow along. You can sign up for a free NewRelic account using the following <a href="https://newrelic.com/signup?via=login">link</a>.</p><h3>API Keys</h3><p>From Account Settings, head over to the API Keys as shown below.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/688/0*o9TEEGaV1Scn90zx" /></figure><p>Create a new <strong>ingest– (or license)</strong> key and provide a friendly name to the key on the following screen.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/890/0*xyLik0P4w5m3wQQh" /></figure><p>Take note of the key being generated. We need to pass on this information to the OpenTelemetry Collector later. In case you already created a key before and you want to reuse the same key you can get a key like so -</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*huUXxbEqJzrBa660" /></figure><h3>SpringBoot application with Log4j2</h3><p>Head over to <a href="https://start.spring.io/">start.spring.io</a> and create a new springboot project as per your liking or simply use this <a href="https://start.spring.io/#!type=maven-project&amp;language=java&amp;platformVersion=2.7.3&amp;packaging=jar&amp;jvmVersion=11&amp;groupId=com.example.otelservice&amp;artifactId=otel-service&amp;name=otel-service&amp;description=Demo%20project%20for%20Spring%20Boot&amp;packageName=com.example.otelservice.otel-service&amp;dependencies=web,devtools,lombok">configuration</a> of the spring initializr to download the projectused in this demo.</p><h3>Additional dependencies</h3><p>Modify the pom.xml file for the project you just created above by adding the following properties/dependencies:</p><h4>Properties</h4><p>Add the following property to the “properties” section of your pom.xml.</p><blockquote>&lt;opentelemetry.version&gt;1.16.0&lt;/opentelemetry.version&gt;</blockquote><h4>Dependencies</h4><p>Head over to the dependencyManagement section and add the following snippet to your pom.xml.</p><blockquote>&lt;dependencyManagement&gt;</blockquote><blockquote>&lt;dependencies&gt;</blockquote><blockquote>&lt;dependency&gt;</blockquote><blockquote>&lt;groupId&gt;io.opentelemetry&lt;/groupId&gt;</blockquote><blockquote>&lt;artifactId&gt;opentelemetry-bom&lt;/artifactId&gt;</blockquote><blockquote>&lt;version&gt;${opentelemetry.version}&lt;/version&gt;</blockquote><blockquote>&lt;type&gt;pom&lt;/type&gt;</blockquote><blockquote>&lt;scope&gt;import&lt;/scope&gt;</blockquote><blockquote>&lt;/dependency&gt;</blockquote><blockquote>&lt;/dependencies&gt;</blockquote><blockquote>&lt;/dependencyManagement&gt;</blockquote><p>Additionally, add the dependencies for Log4j2 and OpenTelemetry, like so:</p><blockquote>&lt;! — Log4j2 →</blockquote><blockquote>&lt;dependency&gt;</blockquote><blockquote>&lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;</blockquote><blockquote>&lt;artifactId&gt;spring-boot-starter-log4j2&lt;/artifactId&gt;</blockquote><blockquote>&lt;/dependency&gt;</blockquote><blockquote>&lt;dependency&gt;</blockquote><blockquote>&lt;groupId&gt;org.apache.logging.log4j&lt;/groupId&gt;</blockquote><blockquote>&lt;artifactId&gt;log4j-layout-template-json&lt;/artifactId&gt;</blockquote><blockquote>&lt;version&gt;2.17.2&lt;/version&gt;</blockquote><blockquote>&lt;/dependency&gt;</blockquote><blockquote>&lt;! — OpenTelemetry →</blockquote><blockquote>&lt;dependency&gt;</blockquote><blockquote>&lt;groupId&gt;io.opentelemetry&lt;/groupId&gt;</blockquote><blockquote>&lt;artifactId&gt;opentelemetry-exporter-otlp&lt;/artifactId&gt;</blockquote><blockquote>&lt;/dependency&gt;</blockquote><blockquote>&lt;dependency&gt;</blockquote><blockquote>&lt;groupId&gt;io.opentelemetry&lt;/groupId&gt;</blockquote><blockquote>&lt;artifactId&gt;opentelemetry-extension-annotations&lt;/artifactId&gt;</blockquote><blockquote>&lt;/dependency&gt;</blockquote><p>Furthermore, we need to ensure that we are excluding Log4j version 1 libraries like so:</p><blockquote>&lt;dependency&gt;</blockquote><blockquote>&lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;</blockquote><blockquote>&lt;artifactId&gt;spring-boot-starter-web&lt;/artifactId&gt;</blockquote><blockquote>&lt;exclusions&gt;</blockquote><blockquote>&lt;exclusion&gt;</blockquote><blockquote>&lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;</blockquote><blockquote>&lt;artifactId&gt;spring-boot-starter-logging&lt;/artifactId&gt;</blockquote><blockquote>&lt;/exclusion&gt;</blockquote><blockquote>&lt;/exclusions&gt;</blockquote><blockquote>&lt;/dependency&gt;</blockquote><h3>New controller and service</h3><p>This is the most boring controller and service you will ever see, but it is good enough for the purpose of this demonstration.</p><h4>GreetingService</h4><p>Create a new service.</p><blockquote>@Service</blockquote><blockquote>@Slf4j</blockquote><blockquote>public class GreetingService {</blockquote><blockquote>@WithSpan</blockquote><blockquote>public String greet(@SpanAttribute String name) {</blockquote><blockquote>Span.<em>current</em>().setAttribute(“param-name”, name);</blockquote><blockquote><em>log</em>.info(“greet service invoked”);</blockquote><blockquote>return String.<em>format</em>(“Hello %s”, name);</blockquote><blockquote>}</blockquote><blockquote>}</blockquote><h4>GreetingController</h4><p>Create a new controller.</p><blockquote>@RestController(“/”)</blockquote><blockquote>@Slf4j</blockquote><blockquote>public class GreetingController {</blockquote><blockquote>private final GreetingService greetingService;</blockquote><blockquote>@Autowired</blockquote><blockquote>public GreetingController(GreetingService greetingService) {</blockquote><blockquote>this.greetingService = greetingService;</blockquote><blockquote>}</blockquote><blockquote>@GetMapping(“/hello”)</blockquote><blockquote>@ResponseBody</blockquote><blockquote>@WithSpan</blockquote><blockquote>public String greet(@RequestParam(“name”) String name) {</blockquote><blockquote><em>log</em>.info(“Greet Controller invoked”);</blockquote><blockquote>return greetingService.greet(name);</blockquote><blockquote>}</blockquote><blockquote>}</blockquote><p>At this point, we have our controller ready and it is communicating with our service. We can test this out by running the application either using the IDE or maven. The following curl should respond with “Hello OpenTelemetry” — curl <a href="http://localhost:8080/hello%5C?name%5C=OpenTelemetry">http://localhost:8080/hello\?name\=OpenTelemetry</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=1948cc819653" width="1" height="1" alt=""><hr><p><a href="https://medium.com/ww-tech-blog/springboot-java-application-observability-with-opentelemetry-newrelic-part-1-1948cc819653">Springboot/Java application observability with OpenTelemetry &amp; NewRelic — Part 1</a> was originally published in <a href="https://medium.com/ww-tech-blog">WW Tech Blog</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Predicting food composition using machine learning]]></title>
            <link>https://medium.com/ww-tech-blog/predicting-food-composition-using-machine-learning-dff0641973b3?source=rss----dfc4c7b378f1---4</link>
            <guid isPermaLink="false">https://medium.com/p/dff0641973b3</guid>
            <dc:creator><![CDATA[Betsey Corey]]></dc:creator>
            <pubDate>Wed, 02 Nov 2022 16:02:43 GMT</pubDate>
            <atom:updated>2022-11-03T18:20:06.363Z</atom:updated>
            <content:encoded><![CDATA[<p>Last January the Data Science team partnered with Columbia University to host an information session that included pitching a capstone project to a class of Business Analytics Masters students. In return, a team of five students selected the WeightWatchers® project to research and ultimately present their findings back to our organization at the end of their spring semester. The project entailed weekly meetings between the students and the Data Science project lead and mentor to ensure guidance around project parameters and to create a sense of community and connectivity throughout. At the conclusion of the project, the Data Science team extended a summer internship to one of the students, Shuning Yang, majoring in Business Analytics. In this post, Shuning highlights her experience interning at WeightWatchers and shares the details of her internship project work that focused on the food composition prediction using machine learning. Shuning was an extremely valuable contribution to the data science team during her internship this summer, and we are looking forward to welcoming her back to WeightWatchers as a full time data scientist after graduating this December!</p><p>Betsey Corey, Director, Strategic Talent Programs &amp; Partnerships</p><p>Personally, I believe that one of the best parts of the WeightWatchers program is the concept of ZeroPoint® foods. These are foods that members can eat without tracking them or deducting any Points® from their daily Points Budget, giving them even more flexibility in what they choose to eat in a day. Earlier this year, WeightWatchers enabled flexible ZeroPoint categories, which adds logic in our food database for determining which foods should be valued at 0 Points.</p><p>This posed a complex problem. Each food has to be reviewed by the nutrition team to calculate its composition and be assigned to the right ZeroPoint foods category. Then a discount value is applied so that tracking these foods won’t deduct Points from a member’s daily Budget if the foods are in the member’s ZeroPoint foods list. For example, if “nonfat yogurt” is a ZeroPoint food for the member, then “vanilla nonfat yogurt” should also be considered as a ZeroPoint food and take no Point off for that member. In addition, this deduction should be applied even if the ZeroPoint food is only <em>partially</em> present in the food. For example, if avocado is a ZeroPoint food for the member, then the corresponding Points for avocado should be deducted from the total points of Chicken Avocado Salad.</p><p>Currently, we have millions of foods in our database. Manually evaluating each food and then assigning it to a category can be a huge undertaking. However, we hypothesized that this is the exact type of complex problem that is actually well suited for machine learning. We set out to discover if our predictions may be true by having Shuning, our data scientist intern, take this on as her summer research project. Our objective was to evaluate and confirm if we can solve this issue using machine learning tools or not. Shuning was able to solve this by using a combination of features and advanced techniques of stacking multiple machine learning models to create a proof of concept that worked really well. So what were the data, models, and results from Shuning’s research? Let’s break it down further:</p><p><strong>Data</strong></p><p>Data used in this project came from multiple sources. WeightWatchers has a presence globally, but to keep things manageable, this project focused on the U.S. market, where we collected a dataset of more than 50,000 foods. Variables used are food type, category, nutrition information, and ingredient statements.</p><p><strong>Model</strong></p><p>The four machine learning models used were decision tree, random forest, XGBoost, and LGBM. Prediction outputs from the four models, harmonic mean of the prediction outputs from all four models (harmean 4), and harmonic mean of the prediction outputs from random forest, XGBoost, and LGBM (harmean 3) are evaluated.</p><p>The metrics used for evaluation were R-squared, RMSE, and accuracy based on different residual thresholds (0.05, 0.1, 0.15, and 0.2). R-squared finds how much of the spread of the data can be explained by the features. The closer to one, the better it is. RMSE gives the difference between predicted value and actual value on average. The closer to zero, the better it is. The prediction output is considered accurate if the difference between actual value and predicted value is within a threshold level that allows for small errors in the estimation.</p><p><strong>Results</strong></p><p>Metrics are compared among different models and prediction outputs. Overall, harmean 3 gives the best result. In addition, two sets of variables are run — one without the ingredient statement and one with the ingredient statement. This is done to check if adding ingredient statements will improve the performance as this feature increases model complexity and places additional demands on data quality. It turned out that ingredient statements are very helpful. There are improvements on all metrics. See the below table for the corn category as an example.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/790/0*pJh7rVUrhdYl2yya" /></figure><p>To test model performance on new data, 55 foods were selected for testing. In general, the prediction outputs are good from an outsider perspective, even though it can sometimes underestimate the percentage of ZeroPoint foods within a dish. But since there are many rules behind food adjustment, further evaluation by the nutrition team is needed.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/622/0*WWohRnvRvaNY1Fdp" /></figure><p><strong>Next steps</strong></p><p>This<strong> </strong>research project has proven that the problem is solvable. The next step is to make modifications and put them into production.</p><p>Shuning Yang — Data Science Intern</p><p><strong>Interested in joining the WW team? </strong>Check out our <a href="https://www.weightwatchers.com/us/corporate-careers">careers page</a> to view technology job listings as well as open positions on other teams.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=dff0641973b3" width="1" height="1" alt=""><hr><p><a href="https://medium.com/ww-tech-blog/predicting-food-composition-using-machine-learning-dff0641973b3">Predicting food composition using machine learning</a> was originally published in <a href="https://medium.com/ww-tech-blog">WW Tech Blog</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Replacing an existing backend service: A retrospective]]></title>
            <link>https://medium.com/ww-tech-blog/replacing-an-existing-backend-service-a-retrospective-f4df68d3014a?source=rss----dfc4c7b378f1---4</link>
            <guid isPermaLink="false">https://medium.com/p/f4df68d3014a</guid>
            <category><![CDATA[node]]></category>
            <category><![CDATA[kafka]]></category>
            <category><![CDATA[javascript]]></category>
            <category><![CDATA[expressjs]]></category>
            <category><![CDATA[postgres]]></category>
            <dc:creator><![CDATA[Alex Oxrud]]></dc:creator>
            <pubDate>Wed, 28 Sep 2022 18:54:17 GMT</pubDate>
            <atom:updated>2022-09-28T18:54:17.858Z</atom:updated>
            <content:encoded><![CDATA[<p>WeightWatchers® encourages members to live a healthy lifestyle, which usually involves improving eating habits, sleeping better, mindfulness, and exercise. Today we are going to talk about how we replaced the legacy health tracking system that’s responsible for tracking sleep, weight, and exercise data and some of the lessons we learned along the way.</p><p><strong>WHY</strong></p><p>The legacy system relied heavily on a third-party service that acted as a middleman between data providers like Apple Health, Fitbit, Garmin, and our WeightWatchers services. They were responsible for integrating with those providers and normalizing the data before sending it to WeightWatchers. There were various drawbacks with the legacy system:</p><ul><li>Did the issue occur on the member’s device? The data provider? The third-party service? WeightWatchers services?</li><li>The database design did not scale well because it wasn’t originally designed to support third-party data and it was hard to track updates to existing data.</li><li>There was little auditability which made it difficult to debug.</li><li>It was expensive to operate and maintain.</li></ul><p>There was an opportunity to rebuild the application so it is reliable and correct to address those pain points and also support future use cases, we called it Health Tracking Service.</p><p><strong>HEALTH TRACKING SERVICE (HTS)</strong></p><p>HTS is responsible for integrating with third-party data providers, storing exercise data, and forwarding sleep and weight data to our respective microservices.</p><p>Since HTS was going to replace an existing system, we used that as an opportunity to deploy the service to the production environment in the background and send it production-level reads and writes which helped us discover issues and address scaling concerns without impacting members.<br>When HTS was ready, we would switch the traffic from the existing system to HTS without the impacting the member’s experience.</p><p>We started the design process by utilizing a diagramming tool that helped us reason about the flow of data and present our ideas and methods to the engineering council for review.</p><p>We ended up creating 26 (and counting) pages of diagrams from very high level to very low level and it helped communicate ideas effectively.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*T-VHN_cnr4mjrepH" /><figcaption>High level diagram of the components that make up the Health Tracking Service</figcaption></figure><p>The Health Tracking System is denoted by the purple colored boxes. At the core of HTS is a Node.js HTTP server that is responsible for handling communication between external clients and internal systems. It is serving tens of thousands of requests per minute to support our WeightWatchers apps and data from third-party data providers like Fitbit. We recognized that providers will send large amounts of data into HTS continuously and processing synchronously would present some performance and scaling challenges. For that reason, we decided to use a streaming architecture where the core of HTS simply queues the provider’s data for asynchronous processing.</p><p><strong>STREAMING ARCHITECTURE</strong></p><p>We used <a href="https://kafka.apache.org/">Apache Kafka</a> for our event-based streaming system, which allowed us to process data in near real time. That grants better auditability since each event is a single immutable event from which we can derive data in a deterministic and repeatable way. The event from each provider would be placed in a provider-specific Kafka topic with multiple partitions. Each partition would have its own consumer, which allowed us to scale each provider independently. Each consumer would be responsible for validating and processing that particular provider’s event.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/816/1*QcFuE25Wo3jE1twaqfsBCQ.png" /><figcaption>An illustration of how Apache Kafka topic, partitions, and consumers might be visualized.</figcaption></figure><p>Before the event would be placed in the topic, we recorded it into an event log database table that would be updated every time the event entered a new phase. The event log entry also helped deduplicate events in the case there was a network issue or the provider sent duplicate requests before the original request was processed.</p><p>We used a hash function on the event as the unique identifier to determine if there is a duplicate entry. This worked well for push events since it contained all necessary data, but presented challenges with ping events. A ping event indicates that there are updates that need to be fetched; so while the event payload is the same, the fetched data from the provider may have changed. In the case of ping events, we added an additional check to see if we’ve seen the hash and it has not been processed.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/870/1*isTp6SlZpsKYZu_0Nz7kSA.png" /><figcaption>De-duplication business logic that supports ping and push type events</figcaption></figure><p><strong>THE EVENT LOG</strong></p><p>One important tool for debugging is the ability to trace data so all events are logged to a PostgreSQL table, which keeps track of the state of the event as it moves through the system.</p><p>It allowed us to have some insights about the system:</p><ul><li>Who is the event for?</li><li>How was it initiated? (provider notification vs manual action)</li><li>Was the event picked up for processing?</li><li>Did the event succeed or fail?</li><li>If it failed, what was the reason?</li><li>How long does it take to process the event?</li></ul><p><strong>THE PROCESSING</strong></p><p>In general, the processing boils down to:</p><ul><li>Fetch incoming data from the provider (in the case of ping events).</li><li>Verify the integrity by validating the provider data against a known schema.</li><li>Transform, filter, and aggregate the data.</li><li>Fetch the member’s previously stored data to compare against the incoming data.</li><li>Store the updated data.</li></ul><p>The event might have information about the member’s exercise, sleep, and weight. HTS makes its best effort to process all the data in the event. If part of the event cannot be processed, it will skip that part and continue processing the rest of the event.</p><p>There are many systems that are involved in processing a single event, which means there is a large surface area for failures.</p><p><strong>REASONS FOR FAILURES</strong></p><p>Failure to process an event can have many culprits, including but not limited to:</p><ul><li>It failed validation because it expected a specific data structure and/or values.</li><li>It failed to fetch data from external or internal systems; maybe there was a network fault, service didn’t respond fast enough, or the service was unavailable.</li><li>It failed to read from the database.</li><li>It failed to write to the database.</li></ul><p><strong>RETRY MECHANISMS</strong></p><p>Because the system was designed with immutable events in mind, it guarantees — no matter if the event gets processed once or multiple times — that it will result in the same set of operations. This makes implementing a retry mechanism easier.</p><p>The retry mechanism consisted of detecting failures and inserting the event into a dead-letter queue, which was just another Kafka topic that would try to process the event again.</p><p>Replaying the event in order to try again is very easy, but it has one significant drawback: If the event failed because a rate limit has been exceeded, then it will quickly retry again. There is no concept of backoffs built in without impacting the overall throughput of the system. HTS retries failed events a couple of times before giving up.</p><p>In the case that all the retries executed during the outage time period, it would have consistently failed and given up. It means that once the external systems are restored, the events are not processed again. This is where our scheduled job comes in that runs every night during off-peak hours that is responsible for retrying failed events in the hope that the outage has been resolved by then.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/860/0*jnrh_xHHXlVybQR0" /><figcaption>Complete look at how events are processed and retried and stored</figcaption></figure><p>The streaming architecture provided by Apache Kafka guarantees that events are delivered in order and have high throughput due to processing events in a distributed manner. The system is able to derive all state updates from a single event thus replaying the event makes it easier to recover from bugs and makes HTS more fault-tolerant.</p><p><strong>APACHE KAFKA NEWBIES</strong></p><p>For my team, this was the first time using Apache Kafka and <a href="https://github.com/Blizzard/node-rdkafka">node-rdkafka</a> and we had made some assumptions about how they worked that were true until they weren’t.</p><blockquote><em>“The bugs that cause these kinds of software faults often lie dormant for a long time until they are triggered by an unusual set of circumstances. In those circumstances, it is relieved that the software is making some kind of assumption about its environment — and while that assumption is usually true, it eventually stops being true for some reason.”</em></blockquote><blockquote>Source: Richard I. Cook: “How Complex Systems Fail,” Cognitive Technologies Laboratory, April 2000.</blockquote><figure><img alt="" src="https://cdn-images-1.medium.com/max/878/0*84sAbOYAuF8_dQGX" /><figcaption>A person attempting to play table tennis using a pool cue stick</figcaption></figure><p><strong>KAFKA: RUNNING HEADLESS</strong></p><p>All Kafka consumers need a connection to the database so they can process an event and update state. If the database is unavailable, then the Kafka consumers are instructed to crash so they can be restarted. It turns out that <a href="https://github.com/Blizzard/node-rdkafka/issues/222#issuecomment-325055014">node-rdkafka operates the consumers in a separate node thread</a>. So when the database went down, it killed the main javascript thread, but the Kafka consumer stayed alive listening to new events and <a href="https://docs.confluent.io/platform/current/clients/consumer.html#offset-management">committing their offset</a>, but never persisting the state into the database.</p><p>When the database encountered a fatal error, the code was calling <em>process.exit(1),</em> which wasn’t picked up by the Kafka consumers. Once we changed our implementation to <em>process.kill(process.pid, “SIGINT”),</em> node-rdkafka was able to detect the going down signal and crashed the consumer as designed.</p><p><strong>KAFKA: RACE CONDITION #1</strong></p><p>Our Quality Assurance (QA) team flagged an issue that sometimes the exercises are duplicated and it was very hard to replicate.</p><p>We noticed that when events were published to the Kafka topic they were assigned random partitions that would be processed by different consumers and create a <a href="https://en.wikipedia.org/wiki/Race_condition">race condition</a>.</p><p>We addressed this issue by using a <a href="https://en.wikipedia.org/wiki/Deterministic_algorithm">deterministic algorithm</a> to place events from the same member in the same partition.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*_LvzdCUColZGASMG5dFOHA.png" /><figcaption>Use the event’s member uuid as the partitioning key</figcaption></figure><p><strong>KAFKA: RACE CONDITION #1.1</strong></p><p>The QA team observed that duplicate data did reduce in frequency, but did not eliminate the problem. The system guarantees that an event from the member will always be processed by the same consumer. However, each consumer was configured to process multiple events in parallel to maximize the throughput. When there were two or more back-to-back events from the same member, it created a race condition.</p><p>Once we understood that multiple events were being processed simultaneously in the same consumer, it revealed a well-known database problem due to the <a href="https://en.wikipedia.org/wiki/Read%E2%80%93modify%E2%80%93write#:~:text=Read%E2%80%93modify%E2%80%93write%20instructions%20often,read%E2%80%93modify%E2%80%93write%20sequences.">read-modify-write</a> approach taken when processing events.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*SeeJbfhbm91-U0oA" /><figcaption>The diagram on the left is the assumption we had, the diagram on the right is what was happening.</figcaption></figure><p>Two events for the same member are picked up for processing in parallel. Each processor makes a database query and arrives at the same conclusion that it needs to create a new record in the database. So both processors create a record resulting in duplicate data.</p><p>Since events from the same member are guaranteed to end up in the same consumer, we could detect back-to-back events using an in-memory hash map and throttle the processing so one event was processed at a time per member while still being able to process multiple members concurrently.</p><p>The event log gave us auditability, the streaming architecture gave us reliability and some fault tolerance, but we needed visibility into performance and availability. That’s where New Relic came into the picture.</p><p><strong>NEW RELIC AND THE MEMORY LEAK</strong></p><p>Integrating with New Relic allowed us to have visibility into the overall health of the system from a single dashboard. We could see the throughput of the systems, any errors raised, and set up alerts in case certain thresholds were exceeded. The integration of New Relic is a simple process of adding the agent to the application and setting up a configuration file. We added New Relic, confirmed it was working, and moved on.</p><p>After exposing our Kafka consumers to production level traffic, we observed that the consumers would consume all available memory and crash. Only to be restarted again and go through the cycle again.</p><p>We thought it was our <a href="https://en.wikipedia.org/wiki/Cache_replacement_policies#Least_recently_used_(LRU)">least recently used caching strategy</a> to avoid querying the database multiple times for the same member, which was somehow growing beyond the established limits.</p><p>We had many theories as to what could be causing the memory leak, but we struggled to find the culprit in the code. This haunted us for a week until an engineer from the platform team reached out to us and said our Kubernetes pods were running out of storage and it revealed that the New Relic agent log files were gigabytes in size. That led us to recheck all the New Relic agent settings and documentation. Under “New Relic Logging Level,” we saw</p><blockquote>“<em>Do not use debug or trace logging unless New Relic Support asks you to use them. These levels of logging can generate excessive overhead. For most situations, use info.”</em></blockquote><blockquote><a href="https://docs.newrelic.com/docs/apm/agents/nodejs-agent/installation-configuration/nodejs-agent-configuration/#logging_config">https://docs.newrelic.com/docs/apm/agents/nodejs-agent/installation-configuration/nodejs-agent-configuration/#logging_config</a></blockquote><p>During the implementation of the New Relic agent, we had set the logging level to “trace” and that made it into the production environment. Changing the logging level back to “info” brought our system back to good health and the requests per minute increased by 10,000.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*SdNiFP-OmuRVTx1L" /><figcaption>New Relic charts showing system metrics spikes and sharp drop offs. After fix was applied, the charts look stable.</figcaption></figure><p><strong>DATABASE: CHANGE DATA CAPTURE</strong></p><p>The PostgreSQL database used by HTS uses <a href="https://debezium.io/">Debezium</a> to stream changes directly from the database. This allowed other microservices within the WeightWatchers ecosystem to subscribe to HTS events by simply listening to a Kafka topic.</p><p>Only the data that is stored in the database is broadcasted, so if downstream services need to interpret the data, they will need to duplicate the business logic already owned by HTS. It was more performant and distributable to store the derived data along with the raw data so downstream services did not have to duplicate the business logic.</p><p>In order to store the derived data, we had to do some migrations and that presented some challenges:</p><p><strong>DATABASE: SCHEMA MIGRATION</strong></p><p>As the HTS requirements evolved, the database schema also changed. This means that some code releases required database schema migrations. These same migrations worked perfectly in our staging environment so we thought it would be safe to run it in production.</p><p>That’s when we relearned that some statements work perfectly fine and fast in small data sets, but become problematic when executed in large data sets. In one particular example, we had an alter table statement that added a column with a calculated value. This caused the entire table to be locked, which halted all processing of the system. Our database administrators were quick to jump in and help us unlock the table and come up with a better migration strategy.</p><p><strong>DATABASE: SECONDARY REPLICA</strong></p><p>The database migrations going forward resulted in spinning up a secondary database that follows the primary database. Run the migrations in the secondary database and then promote the secondary as the primary and drop the old primary.</p><p>This approach worked very well, but one time we were too eager to shut down the old primary and we did so before the replication could finish, so we ended up losing data. We were, however, able to recover the data by restoring the old primary and manually copying over missing records.</p><p><strong>DATABASE: THE SEQUENCE</strong></p><p>Spinning up the secondary database to do migrations has been very successful, but at some point during the evolution of the system, we made a decision to introduce a sequential primary key to the event logs table, which receives a lot of IOPS. This decision forced us to better coordinate the switchovers between old primary and secondary because we had to determine the rate at which the sequence increments over a period of time and then set the starting sequence in the secondary at a much higher level so the replication didn’t create conflicting entries. In retrospect, the only reason for the sequence was merely for comparing events age by looking at the IDs and to allow easy ordering of the set which all could’ve been done by the “created at” column.</p><p><strong>DATABASE: MAX CONNECTIONS</strong></p><p>The database selected for HTS started small and we increased it in size as we needed it. HTS started with the goal of supporting three providers and quickly grew to nine (and counting) different providers. Each provider has many consumers that all connect to the database.</p><p>When a new deployment is made, new Kubernetes pods are brought online and then traffic is slowly switched over to the new pods and the old pods are brought down.</p><p>For a brief period of time during the deployment, the database connections almost doubled. This caused us to reach the max connections allowed by the database so this meant that new deployments could not connect to the database and would be stuck in a crash loop and the deployment would fail.</p><p>The about 300 pods that help power HTS each maintained a connection pool of 10 and 90% of them were idle. We have bumped the max database connections allowed, reduced some unnecessary pods, and switched to <a href="https://www.pgbouncer.org/">pgbouncer</a>, which is a connection pooler for PostgreSQL.</p><p>The switch to pgbouncer reduced the connections to PostgreSQL by 80%, which also reduced CPU usage by 30%, increased available memory by 25%, and reduced IOPS by 30%.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*YJyCrwZnc3i_Fgbs" /><figcaption>Decline in connections once pgbouncer began to be utilized</figcaption></figure><p><strong>CONCLUSION</strong></p><p>Over the last seven days, the system has averaged 41,900 rpm and had a peak of 85,600 rpm.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/651/0*E5dL7qDbuflcIuO1" /><figcaption>Average traffic pattern of Health Tracking Service from Sept 20 — Sept 26</figcaption></figure><p>The official launch of the Health Tracking Service in early August 2022 went very smoothly and didn’t experience any significant issues because we battle tested the system with production traffic in the background. There were a lot of things we learned that we will keep in mind as we continue to grow and build new systems.<strong> </strong>This wouldn’t have been possible without the help of the amazing and talented people of WeightWatchers.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=f4df68d3014a" width="1" height="1" alt=""><hr><p><a href="https://medium.com/ww-tech-blog/replacing-an-existing-backend-service-a-retrospective-f4df68d3014a">Replacing an existing backend service: A retrospective</a> was originally published in <a href="https://medium.com/ww-tech-blog">WW Tech Blog</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[The WW / WeightWatchers Internship Experience: Part III]]></title>
            <link>https://medium.com/ww-tech-blog/the-ww-weightwatchers-internship-experience-part-iii-cc46133310b8?source=rss----dfc4c7b378f1---4</link>
            <guid isPermaLink="false">https://medium.com/p/cc46133310b8</guid>
            <category><![CDATA[summer-internship-program]]></category>
            <category><![CDATA[internships]]></category>
            <dc:creator><![CDATA[Jinny Lee]]></dc:creator>
            <pubDate>Mon, 19 Sep 2022 20:50:11 GMT</pubDate>
            <atom:updated>2022-09-19T21:03:10.347Z</atom:updated>
            <content:encoded><![CDATA[<p>👋 Hey There! Today, we close out our summer intern experience mini-series with part III, ft. Ethan, our Product Management intern, and Ayomikun (Daniel) Adegoke, our Mobile Engineering intern 🌟</p><p><em>📖 ICYMI </em>👉 <em>Check out </em><a href="https://medium.com/ww-tech-blog/impact-community-summer-fun-the-ww-weightwatchers-internship-experience-e015ac1a75c5"><strong><em>part I</em></strong></a><strong><em> </em></strong><em>&amp; </em><a href="https://medium.com/ww-tech-blog/the-ww-weightwatchers-internship-experience-part-ii-5c38e4c4b413"><strong><em>part II</em></strong></a></p><h4>First up, let’s hear from Ethan:</h4><blockquote>“This past summer, I worked as a Product Management intern at WeightWatchers. As part of the member education team, my project focused on determining the efficacy of various in-app messaging patterns, more specifically with educating users on how to successfully interact with WW’s application. I was pleasantly surprised that, at such a large company, I was able to do work that would be seen by real WeightWatchers members and would directly impact WW’s application.</blockquote><blockquote>The organized structure of this internship led directly to its impact and success. I was paired with an amazing manager who included me in important meetings, taught me about the product management role during weekly educational sessions, and answered any questions that I had. In addition, I learned a lot from different speaker sessions from high-level employees who were focused on educating the interns on various aspects of the company. Finally, all of WW’s employees were very approachable and welcoming. I connected with, networked, and learned from team members and fellow intern peers.</blockquote><blockquote>I have never learned as much as I have in such a short period of time as I had during this internship. In just 11 weeks, I enhanced my product management expertise and learned to effectively roadmap a product, lead a team of designers, engineers, and analysts, and use software such as Jira and Figma. I also improved my decision-making skills and learned how to leverage research and data to make informed decisions and successfully prioritize the decisions I make. Finally, I further developed my communication skills, learned how to listen and adapt to feedback, and experienced what it takes to cultivate a collaborative team environment. I truly valued my internship experience at WeightWatchers, and I know that the skills I learned throughout the program will help me throughout my life, and my career.</blockquote><p><strong>-Ethan Sims, Product Management Intern</strong></p><h4>Thanks for sharing, Ethan! Up next, we’ll hear from Ayomikun, our Mobile Engineering intern:</h4><blockquote>“This summer, I worked as a Mobile (Android) Engineering Intern on the growth team. My tasks throughout the program focused on projects that laddered up to the organization’s broader effort to modernize its codebase.</blockquote><blockquote>From the first day of my internship, my manager made me feel so comfortable and an important part of the team. I was provided with all of the resources I needed to succeed and most importantly, the team was always ready and willing to help troubleshoot any challenges I faced.</blockquote><blockquote>In my third week of WW’s internship program, I had already begun contributing to the codebase which was a <strong>very exciting moment </strong>for me. The work culture at WeightWatchers is something I have never experienced before. Everyone wants you to succeed.”</blockquote><p><strong>-</strong><a href="https://medium.com/u/514d74e65246"><strong>Ayomikun Adegoke</strong></a><strong>, Mobile Engineering Intern</strong></p><p>Thank you, Ethan and Ayomikun for sharing your internship journey with us 🙌</p><p>To our readers, I hope that you enjoyed reading through our three-part mini-series which highlights some of our Technology and Product interns’ summer internship experiences and the tremendous impact they made throughout our 11-week program.</p><p>⭐ <strong>Interested in joining the WW team? </strong>Check out our<a href="https://www.weightwatchers.com/us/corporate-careers"> careers page</a> to view technology job listings as well as open positions on other teams.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=cc46133310b8" width="1" height="1" alt=""><hr><p><a href="https://medium.com/ww-tech-blog/the-ww-weightwatchers-internship-experience-part-iii-cc46133310b8">The WW / WeightWatchers Internship Experience: Part III</a> was originally published in <a href="https://medium.com/ww-tech-blog">WW Tech Blog</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[The WW / WeightWatchers Internship Experience: Part II]]></title>
            <link>https://medium.com/ww-tech-blog/the-ww-weightwatchers-internship-experience-part-ii-5c38e4c4b413?source=rss----dfc4c7b378f1---4</link>
            <guid isPermaLink="false">https://medium.com/p/5c38e4c4b413</guid>
            <category><![CDATA[internships]]></category>
            <category><![CDATA[summer-internships]]></category>
            <dc:creator><![CDATA[Jinny Lee]]></dc:creator>
            <pubDate>Fri, 09 Sep 2022 15:11:57 GMT</pubDate>
            <atom:updated>2022-09-09T15:38:29.801Z</atom:updated>
            <content:encoded><![CDATA[<p>👋 Welcome back and thanks for tuning into part II of our intern experience mini-series ft. our Engineering and Product interns!</p><p>Last week, we highlighted our awesome <a href="https://medium.com/ww-tech-blog/impact-community-summer-fun-the-ww-weightwatchers-internship-experience-e015ac1a75c5">Summer Internship Program</a> and kicked off our mini-series with Vineet, our Mobile Engineering intern (thanks, Vineet!) Up next, we’ll hear from Leonel, our Product Design Intern, and Reed, our Mobile Engineering Intern!</p><p><em>Leonel, take it away!</em></p><p>“My internship at WeightWatchers this past summer was truly amazing. As a Product Design Intern, I was challenged with several meaningful projects throughout the 11-week program that helped to hone my skills as a designer.</p><p>This summer, I had the opportunity to be a part of the Growth Product Design team where I conducted rapid experiments, and assisted with auditing and reconstructing the design system for our WW guest site. I also had the amazing opportunity to be part of our home page redesign.</p><p>Everyone I’ve met throughout the 11-week program was incredibly welcoming. From the moment I began my internship, my team made me feel as if we were part of a family. The people at WeightWatchers are very respectful and open-minded. I was encouraged to share my ideas and opinions and always felt comfortable voicing my thoughts and feedback.</p><p>WeightWatchers does an amazing job with their internship program. In addition to the impactful projects we were assigned, we also participated in networking opportunities with one another through Zoom Pods, engaged in team-bonding activities, and worked together on fun side projects. Because of this amazing program, I am leaving with new product design skills, amazing friends, and career references.</p><p><strong>-Leonel Duran Jimenez, Product Design Intern</strong></p><p><em>Thanks so much for sharing, Leonel! We’re now passing the mic to Reed, our Mobile Engineering intern! </em><a href="https://emojipedia.org/microphone/">🎤</a></p><p>“As an iOS Mobile Engineering Intern at WW / WeightWatchers, I was able to work on projects with other engineers and my own project that I presented at the conclusion of our internship program. A highlight of mine while working at WW was being able to engineer a feature using on-device machine learning. The process included researching new technology, creating a prototype, presenting my findings, implementing it in the app, and testing. It was both an exciting and engaging experience.</p><p>The people at WeightWatchers are incredible and the connections I made here are invaluable. Throughout my internship, I was able to lean on other engineers at the organization as resources and they were always happy to help. Managers and leaders of WW are very tactful and truly wish to see their coworkers succeed. The feedback I received on my code reviews helped me to become a better engineer and write cleaner code. Although I was remote throughout the internship, the daily meetings with my team helped me to feel and stay connected. The agile process at WW is empowered by the great scrum masters who effectively keep things organized and moving. The people at WW made my internship experience exceptional.”</p><p><strong>-Reed Hunsaker, Mobile Engineering Intern</strong></p><p>Thank you Leonel &amp; Reed for sharing your incredible intern experience with us! Stay tuned for part III, next week!</p><p>⭐ <strong>Interested in joining the WW team? </strong>Check out our <a href="https://www.weightwatchers.com/us/corporate-careers">careers page</a> to view technology job listings as well as open positions on other teams.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=5c38e4c4b413" width="1" height="1" alt=""><hr><p><a href="https://medium.com/ww-tech-blog/the-ww-weightwatchers-internship-experience-part-ii-5c38e4c4b413">The WW / WeightWatchers Internship Experience: Part II</a> was originally published in <a href="https://medium.com/ww-tech-blog">WW Tech Blog</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
    </channel>
</rss>