{
  "version": 3,
  "sources": ["ssg:https://framerusercontent.com/modules/KGj2E7Cqv23OoB7HKRr8/s3Y9C836LuNFS0PnsflH/lP6yKOgUj-5.js"],
  "sourcesContent": ["import{jsx as e,jsxs as t}from\"react/jsx-runtime\";import{ComponentPresetsConsumer as a,Link as n}from\"framer\";import{motion as i}from\"framer-motion\";import*as r from\"react\";import o from\"https://framerusercontent.com/modules/pVk4QsoHxASnVtUBp6jr/QVzZltTawVJTjmjAWG3C/CodeBlock.js\";export const richText=/*#__PURE__*/t(r.Fragment,{children:[/*#__PURE__*/e(\"p\",{children:\"Finding the length of a string in JavaScript is simple, you use the .length property and that\u2019s it, right?\"}),/*#__PURE__*/t(\"p\",{children:[\"Not so fast. The \u201Clength\u201D of a string may not be exactly what you expect. It turns out that the string length property is the number of\\xa0\",/*#__PURE__*/e(\"em\",{children:\"code units\\xa0\"}),\"in the string, and not the number of characters (or more specifically\",/*#__PURE__*/e(n,{href:\"https://en.wikipedia.org/wiki/Grapheme\",nodeId:\"lP6yKOgUj\",openInNewTab:!0,smoothScroll:!1,children:/*#__PURE__*/e(\"a\",{children:\"\\xa0graphemes\"})}),') as we might expect. For example; \"\uD83D\uDE03\" has a length of 2, and \"\uD83D\uDC71\u200D\u2642\uFE0F\"\\xa0has a length of 5!']}),/*#__PURE__*/e(\"img\",{alt:\"undefined\",className:\"framer-image\",\"data-framer-asset\":\"data:framer/asset-reference,DKgHQPtglWQKjx0D0YDXj6PDcc0.png?originalFilename=wranglercolumnwidth-3.png\",\"data-framer-height\":\"151\",\"data-framer-width\":\"300\",height:\"75\",src:\"https://framerusercontent.com/images/DKgHQPtglWQKjx0D0YDXj6PDcc0.png\",style:{aspectRatio:\"300 / 151\"},width:\"150\"}),/*#__PURE__*/e(\"p\",{children:/*#__PURE__*/e(\"span\",{style:{\"--framer-text-color\":\"rgb(128, 128, 128)\"},children:/*#__PURE__*/e(\"em\",{children:\"Screenshot from Etleap\u2019s data wrangler where the column width depends on the column contents.\"})})}),/*#__PURE__*/e(\"p\",{children:\"In our application we have a data wrangler that lets you view a sample of your data in a tabular format. Since this table supports infinite scrolling, both rows and columns are rendered on demand as you scroll vertically or horizontally. We can\u2019t render all the rows and columns at once since a table could easily include more than a hundred thousand cells, which would bring the browser to its knees.\"}),/*#__PURE__*/e(\"p\",{children:/*#__PURE__*/e(\"em\",{children:\"\u201CThe \u2018length\u2019 of a string may not be exactly what you expect.\u201D\"})}),/*#__PURE__*/e(\"p\",{children:\"Imagine if most rows of a column contains a small amount of data, such as a single word, but a single row contains more data, such as a sentence. If this row is outside of the currently viewed area we don\u2019t want the column to expand as you scroll down, and we definitely don\u2019t want to cram the sentence into the same small space that\u2019s required by the word. This means that we need to find the widest cell in the column before rendering all the cells. It\u2019s fast and straightforward to find the length of the content in each cell, however what if the cell contains emojis or other content where we can\u2019t rely on the length property to give us an accurate value?\"}),/*#__PURE__*/e(\"h2\",{children:\"Code units vs. code points\"}),/*#__PURE__*/t(\"p\",{children:[\"Let\u2019s do a quick Unicode recap. Each character in Unicode is identified by a unique code\",/*#__PURE__*/e(\"em\",{children:\"\\xa0point\"}),\"\\xa0represented by a number between 0 and\\xa010FFFF.\\xa0\\xa0Unfortunately, 10FFFF is a large number and requires 4 bytes to represent. To prevent having to allocate 4 bytes for each character, Unicode also specifies different encoding standards that can be used to interpret it, including UTF-16 which is the internal string encoding used by JavaScript.\"]}),/*#__PURE__*/t(\"p\",{children:[\"UTF-16 is a variable length encoding, which means that it uses either 2 or 4 bytes for each code point depending on what is required. To differentiate, we say that UTF-16 uses one or two code\",/*#__PURE__*/e(\"em\",{children:\"\\xa0units\"}),\"\\xa0to represent one Unicode\\xa0code\",/*#__PURE__*/e(\"em\",{children:\"\\xa0point\"}),\". The most used characters all fit into one code unit, however some of the more exotic characters, such as emojis, require two code units.\"]}),/*#__PURE__*/e(\"p\",{children:/*#__PURE__*/e(\"em\",{children:\"\u201CIt turns out that code points are not the only caveat regarding string lengths in JavaScript.\u201D\"})}),/*#__PURE__*/t(\"p\",{children:[\"This is where a problem arises. Since the .length property returns the number of code\\xa0\",/*#__PURE__*/e(\"em\",{children:\"units\"}),\", and not the number of code\\xa0\",/*#__PURE__*/e(\"em\",{children:\"points\"}),', it does not directly map to what you may expect. As an example, the emoji \"\u263A\uFE0F\" has a length of 2, even though it looks like only one character.']}),/*#__PURE__*/t(\"p\",{children:[\"How can we work around this? ES2015 introduced ways of splitting a string into its respective code points by providing a\",/*#__PURE__*/e(n,{href:\"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/@@iterator\",nodeId:\"lP6yKOgUj\",openInNewTab:!0,smoothScroll:!1,children:/*#__PURE__*/e(\"a\",{children:\"\\xa0string iterator\"})}),\". Both Array.from and the spread operator [\u2026string] uses this internally so both can be used to get the length of a string in code points.\"]}),/*#__PURE__*/e(\"h2\",{children:\"Combining Characters\"}),/*#__PURE__*/t(\"p\",{children:[\"It turns out that code points are not the only caveat regarding string lengths in JavaScript. Another is\",/*#__PURE__*/e(n,{href:\"https://en.wikipedia.org/wiki/Combining_character#Unicode_ranges\",nodeId:\"lP6yKOgUj\",openInNewTab:!0,smoothScroll:!1,children:/*#__PURE__*/t(\"a\",{children:[\"\\xa0\",/*#__PURE__*/e(\"em\",{children:\"combining characters\"})]})}),/*#__PURE__*/e(\"em\",{children:\".\\xa0\"}),'A combining character is a character that doesn\u2019t stand on its own, but rather modifies the other characters around it. This is supported in Unicode, meaning that characters such as \u201Ce\u0300\u201D is actually made up of two code points, \u201Ce\u201D and\\xa0 \u201C\\\\u0300\u201D.such as \"\uD83D\uDC71\u200D\u2642\uFE0F\" which is a combination of \" \uD83D\uDC71\" and \" \u2642\" with a zero width joiner (\\uDC71) in between.']}),/*#__PURE__*/t(\"p\",{children:[\"Working around this is more complicated. Currently there is no built in way of reliably counting graphemes in JavaScript. A current stage 2\",/*#__PURE__*/e(n,{href:\"https://github.com/tc39/proposal-intl-segmenter\",nodeId:\"lP6yKOgUj\",openInNewTab:!0,smoothScroll:!1,children:/*#__PURE__*/e(\"a\",{children:\"\\xa0proposal\"})}),\"\\xa0suggests adding Intl.Segmenter which will return the number of graphemes in a string, however there\u2019s no guarantee that it will make it into the spec (there\u2019s a\",/*#__PURE__*/e(n,{href:\"https://gist.github.com/inexorabletash/8c4d869a584bcaa18514729332300356\",nodeId:\"lP6yKOgUj\",openInNewTab:!0,smoothScroll:!1,children:/*#__PURE__*/e(\"a\",{children:\"\\xa0polyfill\"})}),\"\\xa0for the proposal if you\u2019re desperate.)\"]}),/*#__PURE__*/e(\"h2\",{children:\"Environment Specific Differences\"}),/*#__PURE__*/t(\"p\",{children:[\"Did you know there\u2019s a\",/*#__PURE__*/e(n,{href:\"https://blog.emojipedia.org/ninja-cat-the-windows-only-emoji/\",nodeId:\"lP6yKOgUj\",openInNewTab:!0,smoothScroll:!1,children:/*#__PURE__*/e(\"a\",{children:\"\\xa0ninja cat\"})}),'\\xa0emoji? Neither did we, because it\u2019s a Windows-only emoji! It\u2019s represented by a combination of \"\uD83D\uDC31\" and \"\uD83D\uDC64\". This means that Windows users will see this combination as one character, while other users will see it as two characters. Depending on the users choice of fonts, they could even see something completely different. You could try to prevent this issue by choosing a specific font for your web app, however that won\u2019t be sufficient as the browser will still search through other fonts on your system if a character is not available in your chosen font.']}),/*#__PURE__*/e(\"p\",{children:/*#__PURE__*/e(\"em\",{children:'\u201CThe various environment specific differences means that there\u2019s generally no way of measuring the rendered width of a string mathematically.\"'})}),/*#__PURE__*/e(\"h2\",{children:\"Checkmate?\"}),/*#__PURE__*/e(\"p\",{children:\"The various environment specific differences means that there\u2019s generally no way of measuring the rendered width of a string mathematically. Therefore, the only way to determine the pixel length is to render it and measure. For our use case in the wrangler, this is exactly what we wanted to avoid in the first place. However there are some optimizations that we can make.\"}),/*#__PURE__*/e(\"p\",{children:\"Instead of rendering all the strings in each column, we can split the strings into their corresponding graphemes and render them individually. This allows us to cache the pixel length of each grapheme we encounter. Since there are substantially fewer graphemes than unique strings in a table, this results in a significant reduction in total rendering. This way we can easily determine the correct width of a column, all while keeping the scrolling snappy and your browser happy.\"})]});export const richText1=/*#__PURE__*/t(r.Fragment,{children:[/*#__PURE__*/e(\"p\",{children:\"Between 15:30 UTC on 8/27 and 14:00 UTC on 8/29 we experienced periods of higher-than-usual pipeline latencies. Between 04:00 and 10:00 UTC on 8/29 most pipelines were completely stopped. At Etleap we want to be transparent about system issues that affect customers, and this post summarizes the timeline of the incident and our team\u2019s response, and what we are doing to prevent a similar incident from happening again.\"}),/*#__PURE__*/e(\"h2\",{children:\"What happened and what was the impact?\"}),/*#__PURE__*/t(\"p\",{children:[\"At around 11:30 UTC on 8/27 our ops team was alerted about spikes in two different metrics: CPU of a\\xa0\",/*#__PURE__*/e(n,{href:\"https://zookeeper.apache.org/\",nodeId:\"lP6yKOgUj\",openInNewTab:!0,smoothScroll:!1,children:/*#__PURE__*/e(\"a\",{children:\"Zookeeper\"})}),\"\\xa0node and\\xa0\",/*#__PURE__*/e(n,{href:\"https://en.wikipedia.org/wiki/Tracing_garbage_collection#Stop-the-world_vs._incremental_vs._concurrent\",nodeId:\"lP6yKOgUj\",openInNewTab:!0,smoothScroll:!1,children:/*#__PURE__*/e(\"a\",{children:\"stop-the-world garbage collection (STW GC)\"})}),\"\\xa0time in a Java process responsible for orchestrating certain ETL activities. The two processes were running in different Docker containers on the same host. From this point onwards we saw intermittent spikes in both metrics and periods of downtime of the orchestration process, until the final fix was put in place at 14:00 UTC on 8/29. Additionally, at 15:30 UTC on 8/27 we received the first alert regarding high pipeline latencies. There were intermittent periods of high latency until 10:00 UTC on 8/29.\"]}),/*#__PURE__*/e(\"h2\",{children:\"Incident Response\"}),/*#__PURE__*/e(\"p\",{children:\"When our ops team received the first alert they followed our incident response playbook in order to diagnose the problem. It includes checking on potential causes such as spikes in usage, recently deployed changes, and infrastructure component health. The team determined that the issue had to do with the component that sets up source extraction activities, but found no other correlations. Suspecting an external change related to a pipeline source was leading to the increased garbage collection activity, they went on to attempt to narrow down the problem in terms of dimensions such as source, source type, and customer. Etleap uses a Zookeeper cluster for things like interprocess locking and rate limiting, and the theory was that a misbehaving pipeline source was causing the extraction logic to put a significant amount of additional load on the Zookeeper process, while at the same time causing memory pressure within the process itself. However, after an exhaustive search it was determined that the problem could not be attributed to a single source or customer. Also, memory analysis of the Java process with garbage collection issues showed nothing out of the ordinary.\"}),/*#__PURE__*/e(\"h2\",{children:\"The Culprit\"}),/*#__PURE__*/t(\"p\",{children:[\"Next, the team looked at the memory situation for the host itself. While each process was running within its defined memory bounds, we found that in aggregate the processes\u2019 memory usage exceeded the amount of physical memory available on the host. The host was configured with a swap space, and while this is often a good practice,\\xa0\",/*#__PURE__*/e(n,{href:\"https://zookeeper.apache.org/doc/r3.3.5/zookeeperAdmin.html\",nodeId:\"lP6yKOgUj\",openInNewTab:!0,smoothScroll:!1,children:/*#__PURE__*/e(\"a\",{children:\"it is not so for Zookeeper\"})}),\": by being forced to swap to disk, Zookeeper\u2019s response times went up, leading to queued requests.\"]}),/*#__PURE__*/e(\"p\",{children:\"In other words, the fact that we had incrementally crossed an overall physical memory limit on this host caused a dramatic degradation of the performance of Zookeeper, which in turn resulted in garbage collection time in a client process. The immediate solution was to increase the physical memory on this host, which had the effect of bringing Zookeeper stats back to normal levels (along with the CPU and STW GC metrics mentioned before).\"}),/*#__PURE__*/e(\"h2\",{children:\"Next steps\"}),/*#__PURE__*/e(\"p\",{children:\"We are taking several steps to prevent a similar issue in the future. First, we are configuring Zookeeper not to use swap space. Second, we\u2019re adding monitoring of the key Zookeeper stats, such as latency and outstanding connections. Third, we are adding monitoring of available host physical memory to make sure we know when pressure is getting high. Any of the three configuration and monitoring improvements in isolation would have led us to find the issue sooner, and all three will help prevent issues like this from happening in the first place.\"}),/*#__PURE__*/e(\"p\",{children:\"While it\u2019s impossible to guarantee there will never be high latencies for some pipelines, periods of high latencies across the board are unacceptable. What made this incident particularly egregious was the fact that it went on for over 40 hours, and the whole Etleap team is sorry that this happened. The long resolution time was in large part because we didn\u2019t have the appropriate monitoring to lead us towards the root cause, and we have learned from this and are putting more monitoring of key components in place going forward.\"})]});export const richText2=/*#__PURE__*/t(r.Fragment,{children:[/*#__PURE__*/e(\"p\",{children:\"I am pleased to announce our integration with Snowflake. This is the second data warehouse we support, augmenting our existing Amazon Redshift data warehouse and our S3/Glue data lake offering.\"}),/*#__PURE__*/e(\"p\",{children:\"Etleap lets you integrate all your company\u2019s data into Snowflake, and transform and model it as necessary. The result is clean and well-structured data in Snowflake that is ready for high-performance analytics. Unlike traditional ETL tools, Etleap does not require engineering effort to create, maintain, and scale. Etleap provides sophisticated data error handling and comprehensive monitoring capabilities. Because it is delivered as a service, there is no infrastructure to maintain.\"}),/*#__PURE__*/e(\"img\",{alt:\"undefined\",className:\"framer-image\",\"data-framer-asset\":\"data:framer/asset-reference,Vy7NfQtfMPf9yShDcHCD179eiwU.png?originalFilename=2019_05_07-etleap-product-graphic.png\",\"data-framer-height\":\"854\",\"data-framer-width\":\"1420\",height:\"427\",src:\"https://framerusercontent.com/images/Vy7NfQtfMPf9yShDcHCD179eiwU.png\",srcSet:\"https://framerusercontent.com/images/Vy7NfQtfMPf9yShDcHCD179eiwU.png?scale-down-to=512 512w,https://framerusercontent.com/images/Vy7NfQtfMPf9yShDcHCD179eiwU.png?scale-down-to=1024 1024w,https://framerusercontent.com/images/Vy7NfQtfMPf9yShDcHCD179eiwU.png 1420w\",style:{aspectRatio:\"1420 / 854\"},width:\"710\"}),/*#__PURE__*/t(\"p\",{children:[\"Like any other pipeline set up in Etleap, pipelines to Snowflake can extract from any of\\xa0\",/*#__PURE__*/e(n,{href:\"https://etleap.com/integrations/\",nodeId:\"lP6yKOgUj\",openInNewTab:!0,smoothScroll:!1,children:/*#__PURE__*/e(\"a\",{children:\"Etleap\u2019s supported sources\"})}),\", including databases, web services, file stores, and event streams. Using Etleap\u2019s interactive data wrangler, users have full control over how data is cleaned, structured, and de-identified before it is loaded into Snowflake. From there, Etleap\u2019s native integration with Snowflake is designed to maximize flexibility for users in specifying attributes such as Snowflake schemas, roles, and cluster keys. Once the data is loaded, Etleap\u2019s SQL-based modeling features can be used to further improve the usability and performance of the data for analytics.\"]}),/*#__PURE__*/e(\"p\",{children:\"Not only does Etleap\u2019s integration with Snowflake provide a seamless user experience, it is also a natural fit technically. Etleap is built on AWS and stores extracted and transformed data in S3. Since Snowflake stores data in S3, loading data into Snowflake is fast and efficient. Architecturally, part of what differentiates Snowflake is its separate, elastic scaling of compute and storage resources. Etleap is built on the same principle, thus enabling it to overcome traditional bottlenecks in ETL by scaling storage and compute resources for extraction and transformation separately and elastically. By taking advantage of AWS building blocks we are able to provide a powerful yet uncomplicated data analytics stack for our customers.\"}),/*#__PURE__*/e(\"p\",{children:\"Etleap is devoted to helping teams build data warehouses and data lakes on AWS, and we offer both hosted and in-VPC deployment options. Like Snowflake, Etleap takes advantage of AWS services such as S3 and EC2 to provide performance and cost benefits not possible with traditional ETL solutions.\"}),/*#__PURE__*/e(\"p\",{children:\"As more and more teams building analytics infrastructure on AWS want to use Snowflake as their data warehouse, offering support for Snowflake was a natural next step for us.\"}),/*#__PURE__*/t(\"p\",{children:[\"If you would like to explore building a Snowflake data warehouse with Etleap, you can sign up for a demo\\xa0\",/*#__PURE__*/e(n,{href:\"https://etleap.com/demo/\",nodeId:\"lP6yKOgUj\",openInNewTab:!0,smoothScroll:!1,children:/*#__PURE__*/e(\"a\",{children:\"here\"})}),\".\"]})]});export const richText3=/*#__PURE__*/t(r.Fragment,{children:[/*#__PURE__*/t(\"p\",{children:[\"I\u2019m excited to tell you about two new features we\u2019re launching today:\\xa0\",/*#__PURE__*/e(\"em\",{children:\"Models\"}),\"\\xa0and\\xa0\",/*#__PURE__*/e(\"em\",{children:\"History Tables\"}),\".\"]}),/*#__PURE__*/e(\"h3\",{children:\"Models\"}),/*#__PURE__*/t(\"p\",{children:[\"Etleap has long supported single-source transformations through data wrangling. This is great for cleaning, structuring, and filtering data, and for removing unwanted data, such as PII, before it is loaded to the destination. Today, we\u2019re announcing the general availability of\\xa0\",/*#__PURE__*/e(\"em\",{children:\"models\"}),\", which enable transformations expressed as SQL queries. Two primary use cases for models are combining data from different sources to build data views optimized for analytics, and aggregating data to speed up analytics queries.\"]}),/*#__PURE__*/t(\"p\",{children:[\"Etleap\\xa0\",/*#__PURE__*/e(\"em\",{children:\"models\"}),\"\\xa0are Redshift tables backed by SQL SELECT queries that you define, running against data that has been loaded to Redshift. Etleap creates tables that are the result of these SELECT queries, and updates these tables incrementally or through full refreshes. Etleap triggers updates based on changes to dependent tables, or on a schedule.\"]}),/*#__PURE__*/e(\"img\",{alt:\"undefined\",className:\"framer-image\",\"data-framer-asset\":\"data:framer/asset-reference,H55ME9rvmDCjQNGoMyC8DtPjU.png?originalFilename=Screenshot_2024-11-29_at_14.47.04.png\",\"data-framer-height\":\"946\",\"data-framer-width\":\"1998\",height:\"473\",src:\"https://framerusercontent.com/images/H55ME9rvmDCjQNGoMyC8DtPjU.png\",srcSet:\"https://framerusercontent.com/images/H55ME9rvmDCjQNGoMyC8DtPjU.png?scale-down-to=512 512w,https://framerusercontent.com/images/H55ME9rvmDCjQNGoMyC8DtPjU.png?scale-down-to=1024 1024w,https://framerusercontent.com/images/H55ME9rvmDCjQNGoMyC8DtPjU.png 1998w\",style:{aspectRatio:\"1998 / 946\"},width:\"999\"}),/*#__PURE__*/e(\"h3\",{children:\"History Tables\"}),/*#__PURE__*/t(\"p\",{children:[\"For regular pipelines into Redshift, Etleap fetches new and updated records from the source. Following transformation, new rows are appended to the destination table, and updated rows are overwritten. This update strategy is known as\\xa0\",/*#__PURE__*/e(n,{href:\"https://en.wikipedia.org/wiki/Slowly_changing_dimension#Type_1:_overwrite\",nodeId:\"lP6yKOgUj\",openInNewTab:!0,smoothScroll:!1,children:/*#__PURE__*/e(\"a\",{children:\"type-1 Slowly Changing Dimensions\"})}),\"\\xa0in data warehouse speak.\"]}),/*#__PURE__*/t(\"p\",{children:[\"Sometimes it\u2019s useful to be able to go back in time and query the past state of a record, or to be able to investigate how a record has changed over time. For this, Etleap now provides the ability to retain the history of a record collection. For this, the technique known as\\xa0\",/*#__PURE__*/e(n,{href:\"https://en.wikipedia.org/wiki/Slowly_changing_dimension#Type_2:_add_new_row\",nodeId:\"lP6yKOgUj\",openInNewTab:!0,smoothScroll:!1,children:/*#__PURE__*/e(\"a\",{children:\"type-2 Slowly Changing Dimensions\"})}),\"\\xa0is often used. Here\u2019s how it works in Etleap: An end-date column is added to the table. When a record is initially inserted into the destination table, this column\u2019s value is null. Whenever the record is changed in the source, instead of overwriting the existing record in the destination table, a new row is appended instead with a null end-date value. The existing record\u2019s end-date value is set to the new record\u2019s update timestamp.\"]}),/*#__PURE__*/e(\"p\",{children:\"Starting today, history tables are available for all pipelines from sources that have a primary key and an update timestamp. To get a history table, check the \u2018retain history\u2019 box during single or batch pipeline setup.\"}),/*#__PURE__*/e(\"img\",{alt:\"undefined\",className:\"framer-image\",\"data-framer-asset\":\"data:framer/asset-reference,IhHYAbBsdQHPHvMHpWFHqTbUdI.png?originalFilename=Screenshot_2024-11-29_at_14.48.21.png\",\"data-framer-height\":\"514\",\"data-framer-width\":\"1022\",height:\"257\",src:\"https://framerusercontent.com/images/IhHYAbBsdQHPHvMHpWFHqTbUdI.png\",srcSet:\"https://framerusercontent.com/images/IhHYAbBsdQHPHvMHpWFHqTbUdI.png?scale-down-to=512 512w,https://framerusercontent.com/images/IhHYAbBsdQHPHvMHpWFHqTbUdI.png 1022w\",style:{aspectRatio:\"1022 / 514\"},width:\"511\"}),/*#__PURE__*/t(\"p\",{children:[\"Want to see these features in action? Request a demo\\xa0\",/*#__PURE__*/e(n,{href:\"https://etleap.com/demo/\",nodeId:\"lP6yKOgUj\",openInNewTab:!0,smoothScroll:!1,children:/*#__PURE__*/e(\"a\",{children:\"here\"})}),\"!\"]})]});export const richText4=/*#__PURE__*/t(r.Fragment,{children:[/*#__PURE__*/e(\"p\",{children:\"Today we\u2019re excited to share that we\u2019ve raised $1.5M from First Round Capital, SV Angel, Liquid2, BoxGroup, and others to continue to scale our enterprise-grade ETL solution for building and managing cloud data warehouses.\"}),/*#__PURE__*/e(\"p\",{children:\"ETL has traditionally been associated with expensive projects that take months of custom development by specialized engineers. We started Etleap because we believe in a world where analytics teams manage their own data pipelines, and IT teams aren\u2019t burdened with complex ETL infrastructure and tedious operations.\"}),/*#__PURE__*/e(\"p\",{children:\"Etleap runs in the cloud and requires no engineering work to set up, maintain, and scale. It helps companies drastically lower the cost and complexity of their ETL solution and improve the usefulness of their data.\"}),/*#__PURE__*/e(\"p\",{children:\"Over the past few years we\u2019ve spent a lot of time with analytics teams in order to understand their challenges and have built features for integration, wrangling, and modeling. It\u2019s a thrill to see data-driven customers, including Airtable, Okta, and AXS, use them. Their analytics teams are pushing the boundaries of what\u2019s possible today, and we\u2019re hard at work building features to help bring their productivity to new levels.\"})]});export const richText5=/*#__PURE__*/t(r.Fragment,{children:[/*#__PURE__*/t(\"p\",{children:[/*#__PURE__*/e(\"em\",{children:\"This recorded session is from DataEngConf NYC 17. Slides are available on the\\xa0\"}),/*#__PURE__*/e(n,{href:\"https://www.dataengconf.com/building-etl-infrastructure-that-analysts-love\",nodeId:\"lP6yKOgUj\",openInNewTab:!0,smoothScroll:!1,children:/*#__PURE__*/e(\"a\",{children:/*#__PURE__*/e(\"em\",{children:\"event page\"})})}),/*#__PURE__*/e(\"em\",{children:\".\"})]}),/*#__PURE__*/e(\"p\",{children:\"There\u2019s an often-quoted statistic that says that data analysts spend 80% of their time preparing data and only 20% actually analyzing it. There\u2019s a lot that we as data engineers can do to help our analytics teams be more productive and spend less time worrying about data preparation. This session discusses common problems in data warehousing infrastructure from the point of view of analytics teams, and suggests practical solutions.\"}),/*#__PURE__*/e(\"p\",{children:\"Watch the session video or read the key takeaways below.\"}),/*#__PURE__*/e(\"h2\",{children:\"Key Takeaways\"}),/*#__PURE__*/e(\"p\",{children:\"An ETL infrastructure that analysts and engineers love has two main properties:\"}),/*#__PURE__*/t(\"ol\",{children:[/*#__PURE__*/e(\"li\",{\"data-preset-tag\":\"p\",children:/*#__PURE__*/e(\"p\",{children:\"It satisfies data analysts\u2019 needs: Correctness, availability, recency, cleanliness and speed, and completeness.\"})}),/*#__PURE__*/e(\"li\",{\"data-preset-tag\":\"p\",children:/*#__PURE__*/e(\"p\",{children:\"It empowers analysts to manage it with ease, while alleviating resource-constrained engineering teams.\"})})]}),/*#__PURE__*/e(\"p\",{children:\"This summary covers reasons for why both those properties are important and should be considered when building out ETL infrastructure, whether starting from scratch or transforming an existing, inefficient system with one that analysts and engineers will love.\"}),/*#__PURE__*/e(\"h2\",{children:\"Part 1: Data Analysts\u2019 Hierarchy of Needs\"}),/*#__PURE__*/e(\"p\",{children:\"For data analysts, there\u2019s a hierarchy where they will only care about the higher levels once the more basic levels are taken care of, and in my experience, it can be roughly classified as follows:\"}),/*#__PURE__*/t(\"ul\",{children:[/*#__PURE__*/e(\"li\",{\"data-preset-tag\":\"p\",children:/*#__PURE__*/t(\"p\",{children:[/*#__PURE__*/e(\"strong\",{children:\"Correct\"}),\"\\xa0\u2013 Data correctness is the most fundamental of analysts\u2019 needs. Building trust in the data is key.\"]})}),/*#__PURE__*/e(\"li\",{\"data-preset-tag\":\"p\",children:/*#__PURE__*/t(\"p\",{children:[/*#__PURE__*/e(\"strong\",{children:\"Available\"}),\"\\xa0\u2013 If data isn\u2019t available\u2014eg, if there\u2019s some infrastructure component going down, or a change to the data that the ETL system isn\u2019t setup to handle\u2014analysts can\u2019t do their job for an indefinite amount of time.\"]})}),/*#__PURE__*/e(\"li\",{\"data-preset-tag\":\"p\",children:/*#__PURE__*/t(\"p\",{children:[/*#__PURE__*/e(\"strong\",{children:\"Up-to-date\"}),\"\\xa0\u2013 Once analysts are confident that data is correct, and that they can rely on it being available the vast majority of the time, this is the next thing they\u2019ll care about.\"]})}),/*#__PURE__*/e(\"li\",{\"data-preset-tag\":\"p\",children:/*#__PURE__*/t(\"p\",{children:[/*#__PURE__*/e(\"strong\",{children:\"Clean and Fast\"}),\"\\xa0\u2013 Now we are transitioning from enabling analysts to helping them be as effective at their jobs as they can be.\"]})}),/*#__PURE__*/e(\"li\",{\"data-preset-tag\":\"p\",children:/*#__PURE__*/t(\"p\",{children:[/*#__PURE__*/e(\"strong\",{children:\"Complete\"}),\"\\xa0\u2013 Does the warehouse have all the data that analysts care about? When analysts realize they have fast access to up-to-date data, they start asking more questions, which leads to demands for more sources of data to be integrated.\"]})})]}),/*#__PURE__*/e(\"h2\",{children:\"Part 2: Opening Up the ETL System to Analysts\"}),/*#__PURE__*/e(\"p\",{children:\"As the size and needs of the analyst team grow, so does the complexity of the ETL system. It needs ongoing maintenance and improvements, and the burden of maintaining this system falls on engineers while analysts are forced to wait for weeks or months.\"}),/*#__PURE__*/e(\"p\",{children:\"We can plot the few number of tasks involved with ETL and data warehousing at on a spectrum:\"}),/*#__PURE__*/e(\"img\",{alt:\"undefined\",className:\"framer-image\",\"data-framer-asset\":\"data:framer/asset-reference,j2avZfUVEfYpvhJtw14Vn551zCM.jpg?originalFilename=christian-etleap-slides-3.jpg\",\"data-framer-height\":\"780\",\"data-framer-width\":\"1040\",height:\"390\",src:\"https://framerusercontent.com/images/j2avZfUVEfYpvhJtw14Vn551zCM.jpg\",srcSet:\"https://framerusercontent.com/images/j2avZfUVEfYpvhJtw14Vn551zCM.jpg?scale-down-to=512 512w,https://framerusercontent.com/images/j2avZfUVEfYpvhJtw14Vn551zCM.jpg?scale-down-to=1024 1024w,https://framerusercontent.com/images/j2avZfUVEfYpvhJtw14Vn551zCM.jpg 1040w\",style:{aspectRatio:\"1040 / 780\"},width:\"520\"}),/*#__PURE__*/e(\"p\",{children:\"The solution is to move the line to the right: make it possible for analysts to maintain and scale the ETL infrastructure with less dependence from engineering.\"}),/*#__PURE__*/e(\"p\",{children:\"Benefits of moving the line to the right:\"}),/*#__PURE__*/t(\"ul\",{children:[/*#__PURE__*/e(\"li\",{\"data-preset-tag\":\"p\",children:/*#__PURE__*/e(\"p\",{children:\"Flexible systems\"})}),/*#__PURE__*/e(\"li\",{\"data-preset-tag\":\"p\",children:/*#__PURE__*/e(\"p\",{children:\"Decouple operational from analytical data systems\"})}),/*#__PURE__*/e(\"li\",{\"data-preset-tag\":\"p\",children:/*#__PURE__*/e(\"p\",{children:\"Faster time-to-insight\"})}),/*#__PURE__*/e(\"li\",{\"data-preset-tag\":\"p\",children:/*#__PURE__*/e(\"p\",{children:\"Data is trusted more\"})}),/*#__PURE__*/e(\"li\",{\"data-preset-tag\":\"p\",children:/*#__PURE__*/e(\"p\",{children:\"Analysts can satisfy all their data needs\"})}),/*#__PURE__*/e(\"li\",{\"data-preset-tag\":\"p\",children:/*#__PURE__*/e(\"p\",{children:\"How to Open the ETL System to Analysts:\"})})]}),/*#__PURE__*/e(\"p\",{children:\"When moving the line of ownership from left-to-right it\u2019s critical to ensure analysts aren\u2019t just tasked with maintaining the ETL system but are empowered to do so.\"}),/*#__PURE__*/e(\"p\",{children:\"Some business intelligence (BI) tools shift the line slightly to the right, for instance, by allowing on-the-fly transformations and storing materialized views in a \u201Ccache layer.\u201D This is a step in the right direction, but still leaves a large part of the ownership spectrum with the resource-constrained engineering team.\"}),/*#__PURE__*/t(\"p\",{children:[\"A data pipeline automation tool like\\xa0\",/*#__PURE__*/e(n,{href:\"https://etleap.com/\",nodeId:\"lP6yKOgUj\",openInNewTab:!0,smoothScroll:!1,children:/*#__PURE__*/e(\"a\",{children:\"Etleap\"})}),\"\\xa0takes care of many maintenance and optimization ETL tasks\u2014such as managing data warehouse schemas\u2014and simplifies the rest\u2014such as adding new data from existing sources\u2014so that analysts can do them on their own.\"]}),/*#__PURE__*/e(\"img\",{alt:\"undefined\",className:\"framer-image\",\"data-framer-asset\":\"data:framer/asset-reference,pbCtBwTzPaJN8g92yYUEmQrEnM.jpg?originalFilename=christian-etleap-slides-2-Mar-11-2021-02-50-18-62-PM.jpg\",\"data-framer-height\":\"780\",\"data-framer-width\":\"1040\",height:\"390\",src:\"https://framerusercontent.com/images/pbCtBwTzPaJN8g92yYUEmQrEnM.jpg\",srcSet:\"https://framerusercontent.com/images/pbCtBwTzPaJN8g92yYUEmQrEnM.jpg?scale-down-to=512 512w,https://framerusercontent.com/images/pbCtBwTzPaJN8g92yYUEmQrEnM.jpg?scale-down-to=1024 1024w,https://framerusercontent.com/images/pbCtBwTzPaJN8g92yYUEmQrEnM.jpg 1040w\",style:{aspectRatio:\"1040 / 780\"},width:\"520\"}),/*#__PURE__*/e(\"p\",{children:\"The end result is an infrastructure that gives analysts the data they need. What\u2019s more, engineers love this infrastructure, too, because there\u2019s less burden on them to maintain or scale the ETL infrastructure.\"})]});export const richText6=/*#__PURE__*/t(r.Fragment,{children:[/*#__PURE__*/e(\"p\",{children:\"React.js is a great library for creating user interfaces consisting of components. In the browser React is used to output DOM elements like divs, sections and.. SVG! The DOM supports SVG elements, so there is nothing stopping us from outputting it inline directly with React. This allows for easy creation of SVG components that are updated with props and state just like any other component.\"}),/*#__PURE__*/e(\"h2\",{children:\"Why SVG?\"}),/*#__PURE__*/t(\"p\",{children:[\"Even though a lot is possible with plain CSS, creating complex shapes like\\xa0\",/*#__PURE__*/e(n,{href:\"https://codepen.io/LukyVj/pen/dobjv\",nodeId:\"lP6yKOgUj\",openInNewTab:!0,smoothScroll:!1,children:/*#__PURE__*/e(\"a\",{children:\"hearts\"})}),\"\\xa0or\\xa0\",/*#__PURE__*/e(n,{href:\"https://codepen.io/fox_hover/pen/EZQKQa\",nodeId:\"lP6yKOgUj\",openInNewTab:!0,smoothScroll:!1,children:/*#__PURE__*/e(\"a\",{children:\"elephants\"})}),\"\\xa0is very difficult and requires a lot of code. This is because you are\\xa0restricted to a limited set of primitive shapes that you have to combine to create more complex ones. SVG on the other hand is an image format and allows you a lot more flexibility in creating custom paths. This makes it much easier to create complex shapes as you are free to create any shape you want. If you need convincing, checkout\\xa0\",/*#__PURE__*/e(n,{href:\"http://slides.com/sarasoueidan/building-better-interfaces-with-svg#/11\",nodeId:\"lP6yKOgUj\",openInNewTab:!0,smoothScroll:!1,children:/*#__PURE__*/e(\"a\",{children:\"these slides\"})}),\"\\xa0from Sara Soueidan\u2019s great talk about SVG UI components.\"]}),/*#__PURE__*/e(\"h2\",{children:\"Our use\"}),/*#__PURE__*/e(\"p\",{children:\"At Etleap we have used React with SVG output in some of our graphical components. A great example of this is our circular progress bar.\"}),/*#__PURE__*/e(\"img\",{alt:\"undefined\",className:\"framer-image\",\"data-framer-asset\":\"data:framer/asset-reference,uxkzDTmYgA1dWapMfRXhLUFrA.jpg?originalFilename=image_%25284%2529.jpg\",\"data-framer-height\":\"260\",\"data-framer-width\":\"294\",height:\"130\",src:\"https://framerusercontent.com/images/uxkzDTmYgA1dWapMfRXhLUFrA.jpg\",style:{aspectRatio:\"294 / 260\"},width:\"147\"}),/*#__PURE__*/e(\"p\",{children:/*#__PURE__*/e(\"span\",{style:{\"--framer-text-color\":\"rgb(128, 128, 128)\"},children:/*#__PURE__*/e(\"em\",{children:\"Circular progress bar used on our dashboard.\"})})}),/*#__PURE__*/e(\"p\",{children:\"This component uses SVG to display the circular progress bar and works just like any other React component. It accepts a few props, including the percentage value to display, and updates whenever new props are received. The reason we opted for SVG in this case was that creating a circular progress bar in CSS is tricky. Using SVG for this was much more appropriate and was straight forward using React to output the SVG markup directly to the DOM, let\u2019s compare the two approaches.\"}),/*#__PURE__*/e(\"h2\",{children:\"SVG Progress Bar\"}),/*#__PURE__*/e(\"p\",{children:\"The essential SVG markup required to render the progress bar is very simple:\"}),/*#__PURE__*/e(i.div,{className:\"framer-text-module\",style:{height:\"auto\",width:\"100%\"},children:/*#__PURE__*/e(a,{componentIdentifier:\"module:pVk4QsoHxASnVtUBp6jr/QVzZltTawVJTjmjAWG3C/CodeBlock.js:default\",children:t=>/*#__PURE__*/e(o,{...t,code:'<svg>\\n  <g transform=\"rotate(-90 100 100)\" viewBox=\"0 0 100 100\">\\n    <circle class=\"ProgressBarCircular-bar-background\" r=\"{radius}\" cx=\"{posX}\" cy=\"{posY}\"></circle>\\n    <circle class=\"ProgressBarCircular-bar\" stroke-dasharray=\"{strokeDashArray}\" stroke-linecap=\"round\" r=\"{radius}\" cx=\"{posX}\" cy=\"{posY}\"></circle>\\n  </g>\\n</svg>',language:\"JSX\"})})}),/*#__PURE__*/e(\"p\",{children:/*#__PURE__*/e(\"br\",{className:\"trailing-break\"})}),/*#__PURE__*/t(\"p\",{children:[\"We need two circles, one for the dark background, and one for the lighter progress display. The circles are transparent, and the stroke of the circles show the progress and background. To show the amount of progress we use a dashed outline for the circle. If the space between the first and second dash is at least the length of the circumference of the circle only one dash will be shown and we can manipulate the length of that dash to show the current progress. We use\\xa0\",/*#__PURE__*/e(\"code\",{children:\"stroke-dasharray\"}),\"\\xa0to specify the length and distance between each dash and\\xa0\",/*#__PURE__*/e(\"code\",{children:\"stroke-linecap: round\"}),\"\\xa0to get rounded ends.\"]}),/*#__PURE__*/e(\"h2\",{children:\"CSS Progress Bar\"}),/*#__PURE__*/e(\"p\",{children:\"Let\u2019s have a look at how we can create a similar progress bar in CSS:\"}),/*#__PURE__*/t(\"p\",{children:[\"Since CSS does not support stroke-dasharray, nor stroke-linecap, we are immediately at a disadvantage, therefore lets simplify the problem and start by creating a pie-chart. We create two circles here as well, one for the background and one for the progress bar. To display progress we need to be able to cut away part of the circle, so that we are left with a pie slice. To make this happen we can use the CSS\\xa0\",/*#__PURE__*/e(\"code\",{children:\"clip\"}),\"\\xa0property (unfortunately it has\\xa0\",/*#__PURE__*/e(n,{href:\"https://developer.mozilla.org/en-US/docs/Web/CSS/clip\",nodeId:\"lP6yKOgUj\",openInNewTab:!0,smoothScroll:!1,children:/*#__PURE__*/e(\"a\",{children:\"been deprecated\"})}),\", and\\xa0the replacement\\xa0\",/*#__PURE__*/e(\"code\",{children:\"clip-path\"}),\"\\xa0has very poor browser support). This enables us to define a rectangle mask for the circle so that we can hide parts of it. The problem is that this only works for a maximum of 50% at a time, so we actually need two of these, one for the right and one for the left\u2026 As you can see; this is already getting pretty complicated, and we have not even looked at how to handle the rounded edges. So to prevent doubling the length of this post we\u2019ll stop here. If you are interested in the full solution (without rounded edges) checkout\\xa0\",/*#__PURE__*/e(n,{href:\"https://medium.com/@andsens/radial-progress-indicator-using-css-a917b80c43f9#.mw03owcdy\",nodeId:\"lP6yKOgUj\",openInNewTab:!0,smoothScroll:!1,children:/*#__PURE__*/e(\"a\",{children:\"this post\"})}),\"\\xa0by\\xa0Anders Ingemann.\"]}),/*#__PURE__*/e(\"h2\",{children:\"When to use SVG\"}),/*#__PURE__*/t(\"p\",{children:[\"SVG should not be a replacement for all graphical user elements, but can be used to more easily achieve tricky UI effects where CSS falls short. The most important difference is that SVG supports custom paths. This means you can create any complex shape you want and easily display it, or use it as a mask. This is especially relevant\\xa0in scenarios involving charts or line drawings. Other interesting features that CSS is lacking includes drawing text along a path, animating paths, and support for a bunch of filters. That being said,\\xa0CSS is catching up with SVG and has seen support for several filters, masks, and even\\xa0\",/*#__PURE__*/e(n,{href:\"https://css-tricks.com/clipping-masking-css/\",nodeId:\"lP6yKOgUj\",openInNewTab:!0,smoothScroll:!1,children:/*#__PURE__*/e(\"a\",{children:\"custom clip paths\"})}),\".\\xa0For now though, if your designer has created some truly fancy UI effect that you instinctually disregard as impossible, perhaps it is a good time to look into SVG and make it a reality after all.\"]})]});export const richText7=/*#__PURE__*/t(r.Fragment,{children:[/*#__PURE__*/e(\"p\",{children:\"To ensure a great user experience it is important to keep the initial page load as \\xa0fast as possible. There are two main ways of doing this; one is to reduce the number of file requests made when the site is loading, and the other is to reduce the size of the files. To automate this it is common to use a tool that combines all your javascript into one minified bundle file.\"}),/*#__PURE__*/e(\"p\",{children:\"This is a great way of improving the load times, but to improve it even further manual intervention is required. One way to reduce the file size is to remove unnecessary code and dependencies. If large parts of your code are not required at the first page load, you could also split your bundle into multiple files to reduce the size of the initial bundle. However, when all your files are combined into one bundle it is difficult to know what it consists of and how big the different components are.\"}),/*#__PURE__*/e(\"h2\",{children:\"Webpack Visualizer\"}),/*#__PURE__*/t(\"p\",{children:[\"At Etleap we use\\xa0\",/*#__PURE__*/e(n,{href:\"https://webpack.github.io/\",nodeId:\"lP6yKOgUj\",openInNewTab:!0,smoothScroll:!1,children:/*#__PURE__*/e(\"a\",{children:\"Webpack\"})}),\"\\xa0to bundle our application. The bundles created are concise and minified, therefore we use \\xa0\",/*#__PURE__*/e(n,{href:\"https://chrisbateman.github.io/webpack-visualizer/\",nodeId:\"lP6yKOgUj\",openInNewTab:!0,smoothScroll:!1,children:/*#__PURE__*/e(\"a\",{children:\"Webpack Visualizer\"})}),\"\\xa0to\\xa0inspect the content. This tool produces a layered pie chart of the included content in the bundle, and allows us to quickly assess the relative size of each file. Here is an example:\"]}),/*#__PURE__*/e(\"img\",{alt:\"undefined\",className:\"framer-image\",\"data-framer-asset\":\"data:framer/asset-reference,zb1aZqiboP8VmFtj4rHCxDXyR9A.png?originalFilename=image_%25281%2529.png\",\"data-framer-height\":\"383\",\"data-framer-width\":\"413\",height:\"191\",src:\"https://framerusercontent.com/images/zb1aZqiboP8VmFtj4rHCxDXyR9A.png\",style:{aspectRatio:\"413 / 383\"},width:\"206\"}),/*#__PURE__*/e(\"p\",{children:\"The chart is categorised by folders, with the root folders at the innermost layer of the chart. Files are orange, folders blue, and dependencies are green. By hovering any slice, the size of the slice relative to the bundle is shown. This makes it easy to identify parts of the application that can be optimized for load time.\"}),/*#__PURE__*/e(\"h2\",{children:\"Reducing dependencies\"}),/*#__PURE__*/t(\"p\",{children:[\"Through bundle inspection we found that\\xa0\",/*#__PURE__*/e(n,{href:\"http://lodash.com/\",nodeId:\"lP6yKOgUj\",openInNewTab:!0,smoothScroll:!1,children:/*#__PURE__*/e(\"a\",{children:\"Lodash\"})}),\"\\xa0accounted for 12% of our bundle. After looking through our code we noticed that we only needed a small set of the functionality Lodash provided. Since it\\xa0supports selective import, ie. only importing the specific functions you require, we managed to reduced the size of Lodash to 2.3%. That is almost a 10% reduction of our total bundle.\"]}),/*#__PURE__*/e(\"img\",{alt:\"undefined\",className:\"framer-image\",\"data-framer-asset\":\"data:framer/asset-reference,LHvdOrBcp47oe6yFzlOkEsfDEHc.jpg?originalFilename=image_%25282%2529.jpg\",\"data-framer-height\":\"405\",\"data-framer-width\":\"437\",height:\"202\",src:\"https://framerusercontent.com/images/LHvdOrBcp47oe6yFzlOkEsfDEHc.jpg\",style:{aspectRatio:\"437 / 405\"},width:\"218\"}),/*#__PURE__*/e(\"p\",{children:\"If a dependency does not allow you to selectively import functionality, sometimes writing your own implementation of a function can shave off several percent of your bundle if it results in dropping a large dependency.\"}),/*#__PURE__*/e(\"h2\",{children:\"Identifying splitting points\"}),/*#__PURE__*/e(\"img\",{alt:\"undefined\",className:\"framer-image\",\"data-framer-asset\":\"data:framer/asset-reference,OOTulp1vfzuaTaZKZVZdE5ca0Ag.jpg?originalFilename=image_%25283%2529.jpg\",\"data-framer-height\":\"436\",\"data-framer-width\":\"471\",height:\"218\",src:\"https://framerusercontent.com/images/OOTulp1vfzuaTaZKZVZdE5ca0Ag.jpg\",style:{aspectRatio:\"471 / 436\"},width:\"235\"}),/*#__PURE__*/e(\"p\",{children:\"One of the key components of Etleap is a wizard for setting up data pipelines. Our wizard alone was responsible for almost 30% of our bundle. Since users load our dashboard on the initial load, and usually use the wizard later, we decided to use this as a splitting point. By moving most of the wizard to a separate bundle, leaving only the essentials for a speedy transition, we managed to reduce our initial bundle size by 20%.\"}),/*#__PURE__*/e(\"h2\",{children:\"How to inspect your own bundle\"}),/*#__PURE__*/t(\"p\",{children:[\"To use the tool you need to generate a statistics file for your bundle by passing in \u2013json as a parameter to Webpack, and saving the output as a file. This file contains detailed information about your bundle (\",/*#__PURE__*/e(\"em\",{children:\"including your source code!\"}),\"). If you are using an npm script to run your Webpack build, you can use this command to create the file:\"]}),/*#__PURE__*/e(i.div,{className:\"framer-text-module\",style:{height:\"auto\",width:\"100%\"},children:/*#__PURE__*/e(a,{componentIdentifier:\"module:pVk4QsoHxASnVtUBp6jr/QVzZltTawVJTjmjAWG3C/CodeBlock.js:default\",children:t=>/*#__PURE__*/e(o,{...t,code:\"npm run <my_script> \u2014 \u2013json > stats.json</my_script>\",language:\"JSX\"})})}),/*#__PURE__*/e(\"p\",{children:/*#__PURE__*/e(\"br\",{className:\"trailing-break\"})}),/*#__PURE__*/t(\"p\",{children:[\"There is also a Webpack\\xa0\",/*#__PURE__*/e(n,{href:\"https://github.com/chrisbateman/webpack-visualizer\",nodeId:\"lP6yKOgUj\",openInNewTab:!0,smoothScroll:!1,children:/*#__PURE__*/e(\"a\",{children:\"plugin\"})}),\"\\xa0that will generate the visualisation in a local .html file, if you prefer to use that.\"]}),/*#__PURE__*/e(\"p\",{children:\"Note that the tool uses the pre-minified bundle so there could be some differences in relative sizes after minification.\"})]});export const richText8=/*#__PURE__*/t(r.Fragment,{children:[/*#__PURE__*/e(\"p\",{children:\"There are a few requirements for a good password reset token:\"}),/*#__PURE__*/t(\"ol\",{children:[/*#__PURE__*/e(\"li\",{\"data-preset-tag\":\"p\",children:/*#__PURE__*/e(\"p\",{children:\"user should be able to reset their password with the token they receive from in an email\"})}),/*#__PURE__*/e(\"li\",{\"data-preset-tag\":\"p\",children:/*#__PURE__*/e(\"p\",{children:\"the token should not be guessable\"})}),/*#__PURE__*/e(\"li\",{\"data-preset-tag\":\"p\",children:/*#__PURE__*/e(\"p\",{children:\"the token should expire\"})}),/*#__PURE__*/e(\"li\",{\"data-preset-tag\":\"p\",children:/*#__PURE__*/e(\"p\",{children:\"user should not be able to re-use token\"})})]}),/*#__PURE__*/t(\"p\",{children:[\"Ideally, the web framework of your choice should already have a built-in way to generate reset tokens. However, we use\\xa0\",/*#__PURE__*/e(n,{href:\"https://www.playframework.com/\",nodeId:\"lP6yKOgUj\",openInNewTab:!0,smoothScroll:!1,children:/*#__PURE__*/e(\"a\",{children:\"Play\"})}),\"\\xa0and it does not provide a way to do that, so we have to roll our own.\"]}),/*#__PURE__*/t(\"p\",{children:[\"There are no shortage of answers on Stack Overflow about\\xa0\",/*#__PURE__*/e(n,{href:\"https://www.google.com/search?q=generate%20reset%20password%20tokens%20site%3Astackoverflow.com&ie=utf-8&oe=utf-8\",nodeId:\"lP6yKOgUj\",openInNewTab:!0,smoothScroll:!1,children:/*#__PURE__*/e(\"a\",{children:\"generating password reset tokens\"})}),\", however all of them suggest adding a reset token to the database. Updating the database for an unauthenticated request irks me, so I decided to implement stateless password reset tokens.\"]}),/*#__PURE__*/e(\"p\",{children:\"In principle, a stateless password reset token is very similar to a stateless session cookie. All the state information is stored in the token itself, and it\u2019s combined with a secret so it\u2019s not possible to generate the token without also knowing the secret. The devil is in the detail.\"}),/*#__PURE__*/e(\"p\",{children:\"The naive way to create the token would look something like:\"}),/*#__PURE__*/e(\"img\",{alt:\"undefined\",className:\"framer-image\",\"data-framer-asset\":\"data:framer/asset-reference,pohur2TOWD4fI5zKuHaU10M6pcs.jpg?originalFilename=image_%25283%2529.jpg\",\"data-framer-height\":\"311\",\"data-framer-width\":\"400\",height:\"155\",src:\"https://framerusercontent.com/images/pohur2TOWD4fI5zKuHaU10M6pcs.jpg\",style:{aspectRatio:\"400 / 311\"},width:\"200\"}),/*#__PURE__*/e(\"p\",{children:/*#__PURE__*/e(\"code\",{children:'user + \" \" + expiration time + \" \" + hash(user + expiration time + secret)'})}),/*#__PURE__*/e(\"p\",{children:\"To verify the token, all we need to do is:\"}),/*#__PURE__*/t(\"ol\",{children:[/*#__PURE__*/e(\"li\",{\"data-preset-tag\":\"p\",children:/*#__PURE__*/e(\"p\",{children:\"parse out user and expiration time\"})}),/*#__PURE__*/e(\"li\",{\"data-preset-tag\":\"p\",children:/*#__PURE__*/e(\"p\",{children:\"check that the token is not expired\"})}),/*#__PURE__*/e(\"li\",{\"data-preset-tag\":\"p\",children:/*#__PURE__*/e(\"p\",{children:\"compute the hash and make sure that it matches\"})})]}),/*#__PURE__*/t(\"p\",{children:[\"Unfortunately, in addition to not satisfying requirement #4 from above, this would also allow a malicious attacker to reset anyone\u2019s password. For example, my username is\\xa0\",/*#__PURE__*/e(\"code\",{children:\"kahing\"}),\", and if an attacker wants to reset my password, what he needs to do is (suppose secret is\\xa0\",/*#__PURE__*/e(\"code\",{children:\"XXX\"}),\"):\"]}),/*#__PURE__*/t(\"ol\",{children:[/*#__PURE__*/e(\"li\",{\"data-preset-tag\":\"p\",children:/*#__PURE__*/t(\"p\",{children:[\"sign up for a new account as\\xa0\",/*#__PURE__*/e(\"code\",{children:\"kahing1\"})]})}),/*#__PURE__*/e(\"li\",{\"data-preset-tag\":\"p\",children:/*#__PURE__*/t(\"p\",{children:[\"reset password for\\xa0\",/*#__PURE__*/e(\"code\",{children:\"kahing1\"}),\", the attacker now has a password reset token for their own account:\",/*#__PURE__*/e(\"code\",{children:\"kahing1 14834409451 hash(kahing11483440945XXX)\"})]})}),/*#__PURE__*/e(\"li\",{\"data-preset-tag\":\"p\",children:/*#__PURE__*/t(\"p\",{children:[\"attacker resets my password with token:\",/*#__PURE__*/e(\"code\",{children:\"kahing 114834409451 hash(kahing11483440945XXX)\"})]})}),/*#__PURE__*/e(\"li\",{\"data-preset-tag\":\"p\",children:/*#__PURE__*/e(\"p\",{children:\"this token would validate and has an expiration time in the year 2333\"})})]}),/*#__PURE__*/t(\"p\",{children:[\"This is called a\\xa0\",/*#__PURE__*/e(\"em\",{children:\"cryptographic splicing attack\"}),\". WordPress\\xa0\",/*#__PURE__*/e(n,{href:\"https://lwn.net/Articles/281327/\",nodeId:\"lP6yKOgUj\",openInNewTab:!0,smoothScroll:!1,children:/*#__PURE__*/e(\"a\",{children:\"suffered from it\"})}),\"\\xa0at one point so don\u2019t feel too bad if you didn\u2019t spot it yourself.\"]}),/*#__PURE__*/e(\"p\",{children:\"In addition to adding a delimiter to the hashed portion, there\u2019s another way to fix this problem and also bring our token to satisfy requirement #4: we can add a per-user secret and hash that as well. So the new token becomes:\"}),/*#__PURE__*/e(\"p\",{children:/*#__PURE__*/e(\"code\",{children:'user + \" \" + expiration time + \" \" + hash(user + \" \" + expiration time + \" \" + user secret + \" \" + application secret)'})}),/*#__PURE__*/t(\"p\",{children:[\"Every time the user resets their password, we generate a new user secret, which would effectively invalidate all the prior tokens. Also,\\xa0\",/*#__PURE__*/e(\"code\",{children:\"kahing1\"}),\"\\xa0will not be able to generate a valid token for\\xa0\",/*#__PURE__*/e(\"code\",{children:\"kahing\"}),\"\\xa0anymore.\"]}),/*#__PURE__*/t(\"p\",{children:[\"It is possible to safely generate a stateless password reset token, just as it is possible to safely generate a stateless session cookie. It helps to read up on best practices and related vulnerabilities for the latter (such as\\xa0\",/*#__PURE__*/e(n,{href:\"https://lwn.net/Articles/283383/\",nodeId:\"lP6yKOgUj\",openInNewTab:!0,smoothScroll:!1,children:/*#__PURE__*/e(\"a\",{children:\"Session cookies for web applications\"})}),\").\"]}),/*#__PURE__*/e(\"p\",{children:\"Happy Hacking!\"}),/*#__PURE__*/e(\"p\",{children:\"(We use SHA256 as the hash in case anyone is curious)\"})]});export const richText9=/*#__PURE__*/t(r.Fragment,{children:[/*#__PURE__*/e(\"p\",{children:\"This post is about how to\\xa0split and process CSV files in pieces! Newline characters within fields makes it tricky, but with the help of a finite-state machine it\u2019s possible to work around that in most real-world cases.\"}),/*#__PURE__*/t(\"p\",{children:[/*#__PURE__*/e(n,{href:\"https://en.wikipedia.org/wiki/Comma-separated_values\",nodeId:\"lP6yKOgUj\",openInNewTab:!0,smoothScroll:!1,children:/*#__PURE__*/e(\"a\",{children:\"Comma-separated values (CSV)\"})}),\"\\xa0is perhaps the world\u2019s most common data exchange format. It\u2019s human-readable, it\u2019s compact, and it\u2019s supported by pretty much any application that ingests data. At Etleap we frequently encounter really big CSV files that would take a long time to process sequentially. Since we want our clients\u2019 data pipelines to have minimal latency,\\xa0we\\xa0split these files into pieces and process them in a distributed fashion.\"]}),/*#__PURE__*/t(\"p\",{children:[\"At this point you\u2019re probably\\xa0wondering what the problem is \u2013 CSVs are pretty simple, and it\u2019s really easy to write a parser for it, right? Well, yes, as it does have a\\xa0\",/*#__PURE__*/e(n,{href:\"http://en.wikipedia.org/wiki/Context-free_grammar\",nodeId:\"lP6yKOgUj\",openInNewTab:!0,smoothScroll:!1,children:/*#__PURE__*/e(\"a\",{children:\"context-free grammar\"})}),\"\\xa0(at least according to the most popular CSV standard,\\xa0\",/*#__PURE__*/e(n,{href:\"http://tools.ietf.org/html/rfc2234\",nodeId:\"lP6yKOgUj\",openInNewTab:!0,smoothScroll:!1,children:/*#__PURE__*/e(\"a\",{children:\"RFC 4180\"})}),\").\\xa0The challenge is\\xa0\",/*#__PURE__*/e(\"strong\",{children:\"splitting\"}),\", in other words,\\xa0how do\\xa0parse the file when you\u2019re\\xa0reading from a point in the file that is not\\xa0the beginning.\"]}),/*#__PURE__*/e(\"p\",{children:\"Again, you\\xa0might be wondering\\xa0why a blog post on this is interesting when splitting files on newlines is a feature that\u2019s been built into\\xa0Hadoop\\xa0for ages. Here\u2019s how Hadoop does it: It assigns responsibility for byte ranges of the file to independent processes, called \u201Cmap tasks\u201D. Each map task scans\\xa0until the next newline after its byte boundary, and starts processing data from that point onwards (unless the map task is reading from the beginning of the file, in which case it starts processing data from byte 0). Similarly, at the end of the byte range, the map task processes data up to the first\\xa0newline after the end. This way, all the data gets processed exactly once overall\\xa0with\\xa0minimal extra IO work.\"}),/*#__PURE__*/t(\"p\",{children:[\"This would work for CSV files had it not been for the fact that CSV files can have\\xa0\",/*#__PURE__*/e(\"strong\",{children:\"newlines embedded within fields\"}),\". In other words, when the map task starts scanning its input split, it\u2019s not sufficient to look for the next newline to decide where to start producing input records, since that newline might be part of a field value as opposed to a record delimiter. Looking for the next quote also won\u2019t work, as there\u2019s no definite way of knowing whether that quote is the beginning or end of a field.\"]}),/*#__PURE__*/t(\"p\",{children:[\"So if looking for a newline or a quote won\u2019t work, how will the map task decide where the next record beings? Let\u2019s look at a\\xa0\",/*#__PURE__*/e(n,{href:\"http://en.wikipedia.org/wiki/Finite-state_machine\",nodeId:\"lP6yKOgUj\",openInNewTab:!0,smoothScroll:!1,children:/*#__PURE__*/e(\"a\",{children:\"finite-state machine\"})}),\"\\xa0for CSV files (we\u2019re assuming the file format follows\\xa0\",/*#__PURE__*/e(n,{href:\"http://tools.ietf.org/html/rfc2234\",nodeId:\"lP6yKOgUj\",openInNewTab:!0,smoothScroll:!1,children:/*#__PURE__*/e(\"a\",{children:\"RFC 4180\"})}),\"). At any point in the file, you can either be within a quoted field or within a non-quoted field, so these are natural candidates for states. You can also be at the end of a field (i.e. after a comma), or at the end of a record (i.e. after a newline). In order to allow for single-character state transitions, one more state is necessary: when you\u2019re in a quoted field and you see a quote. In that case, the quote might either signify the end of the quoted field, or an escaped quote (if the next character is a quote). Our alphabet consists of newline (CR or LF), comma, quote, and a character class containing all other symbols, and the resulting finite-state machine (FSM) is shown below.\"]}),/*#__PURE__*/e(\"img\",{alt:\"undefined\",className:\"framer-image\",\"data-framer-asset\":\"data:framer/asset-reference,QoUIvdD3Zfm0oAHgnwgEmyORYQ.jpg?originalFilename=image_%25285%2529.jpg\",\"data-framer-height\":\"725\",\"data-framer-width\":\"1040\",height:\"362\",src:\"https://framerusercontent.com/images/QoUIvdD3Zfm0oAHgnwgEmyORYQ.jpg\",srcSet:\"https://framerusercontent.com/images/QoUIvdD3Zfm0oAHgnwgEmyORYQ.jpg?scale-down-to=512 512w,https://framerusercontent.com/images/QoUIvdD3Zfm0oAHgnwgEmyORYQ.jpg?scale-down-to=1024 1024w,https://framerusercontent.com/images/QoUIvdD3Zfm0oAHgnwgEmyORYQ.jpg 1040w\",style:{aspectRatio:\"1040 / 725\"},width:\"520\"}),/*#__PURE__*/e(\"p\",{children:/*#__PURE__*/e(\"span\",{style:{\"--framer-text-color\":\"rgb(128, 128, 128)\"},children:/*#__PURE__*/e(\"em\",{children:\"Figure 1: Finite-state machine for CSV files. AR is the start state, and all states\\xa0except Q are accepting\"})})}),/*#__PURE__*/t(\"p\",{children:[\"The content of a CSV file will be accepted if and only if it is accepted by this FSM. Note that there are symbols that aren\u2019t allowed in certain states, such as a quote symbol (\",/*#__PURE__*/e(\"em\",{children:\"Q\"}),\") within an unquoted field (\",/*#__PURE__*/e(\"em\",{children:\"UQ\"}),\"). As is illustrated above,\\xa0\",/*#__PURE__*/e(\"em\",{children:\"AR\"}),\"\\xa0is the start state, since being at the beginning of a CSV file is like just having finished a record from a parsing perspective.\"]}),/*#__PURE__*/e(\"p\",{children:\"The reason why I\u2019m bringing up the FSM of a CSV file is that it turns out to be useful for finding the next record when parsing a piece of a CSV file. The general idea is to assume when we\u2019re starting from a point in the file that is not the beginning that we could be in any of the five states above. As we start consuming character input, that gives us five possible next states. However, as you can see from the transition function table below, there are only two possible states after consuming the first input symbol.\"}),/*#__PURE__*/e(\"img\",{alt:\"undefined\",className:\"framer-image\",\"data-framer-asset\":\"data:framer/asset-reference,DzJjeyGaSn6agvibvHJKvSeGO0.png?originalFilename=Screenshot_2024-11-29_at_15.16.05.png\",\"data-framer-height\":\"356\",\"data-framer-width\":\"978\",height:\"178\",src:\"https://framerusercontent.com/images/DzJjeyGaSn6agvibvHJKvSeGO0.png\",srcSet:\"https://framerusercontent.com/images/DzJjeyGaSn6agvibvHJKvSeGO0.png?scale-down-to=512 512w,https://framerusercontent.com/images/DzJjeyGaSn6agvibvHJKvSeGO0.png 978w\",style:{aspectRatio:\"978 / 356\"},width:\"489\"}),/*#__PURE__*/t(\"p\",{children:[\"After the first symbol, we can simply follow the two paths until we find one of the two paths to be invalid, that is we encounter a non-special character (\",/*#__PURE__*/e(\"em\",{children:\"O\"}),\") from the\\xa0\",/*#__PURE__*/e(\"em\",{children:\"AQ\\xa0\"}),\"state or a quote character (\",/*#__PURE__*/e(\"em\",{children:\"Q\"}),\") in the\\xa0\",/*#__PURE__*/e(\"em\",{children:\"UQ\"}),\"\\xa0state. The path that is left \u201Cwins\u201D, and the first byte\\xa0for processing\\xa0is the\\xa0one after that path\u2019s first entry into\\xa0\",/*#__PURE__*/e(\"em\",{children:\"AR\"}),\"\\xa0\u2013 this is similar to how Hadoop works for text files, as described earlier. The input up until that point in the file will have been consumed by the task processing the previous file split. Note that because of the observation above about the state after the first symbol we can use the two states\\xa0\",/*#__PURE__*/e(\"em\",{children:\"Q\"}),\"\\xa0and\\xa0\",/*#__PURE__*/e(\"em\",{children:\"AR\"}),\"\\xa0as representative starting states.\"]}),/*#__PURE__*/e(\"h2\",{children:\"CSVs Without Quotes\"}),/*#__PURE__*/t(\"p\",{children:[\"So is that it? Well it turns out we\u2019re not quite done. A map task can read\\xa0the input only once so we have to keep in memory the input we read for the purpose of figuring out the starting point until we know what to process. Since memory is a scarce resource we have to place a bound on that memory buffer, which could lead to not finding the starting point. This isn\u2019t necessarily just something that happens in edge cases \u2013 consider for example the case where the CSV doesn\u2019t have any quotes at all: The path that starts in\\xa0\",/*#__PURE__*/e(\"em\",{children:\"Q\"}),\"\\xa0will stay in that state forever, whereas the other path will follow a path involving\\xa0\",/*#__PURE__*/e(\"em\",{children:\"UQ\"}),\",\\xa0\",/*#__PURE__*/e(\"em\",{children:\"AF\"}),\", and\\xa0\",/*#__PURE__*/e(\"em\",{children:\"AR\"}),\". To get around this problem we could place a maximum allowed record length, and set our buffer size accordingly. The FSM below accepts CSVs with a maximum record length of\\xa0\",/*#__PURE__*/e(\"em\",{children:\"N\"}),\"\\xa0characters. Note that commas and quotes also contribute to the record length in this model.\"]}),/*#__PURE__*/e(\"img\",{alt:\"undefined\",className:\"framer-image\",\"data-framer-asset\":\"data:framer/asset-reference,vEbdAy1Wk9UEYSYzDhsM9KbT2s.jpg?originalFilename=image_%25286%2529.jpg\",\"data-framer-height\":\"527\",\"data-framer-width\":\"1040\",height:\"263\",src:\"https://framerusercontent.com/images/vEbdAy1Wk9UEYSYzDhsM9KbT2s.jpg\",srcSet:\"https://framerusercontent.com/images/vEbdAy1Wk9UEYSYzDhsM9KbT2s.jpg?scale-down-to=512 512w,https://framerusercontent.com/images/vEbdAy1Wk9UEYSYzDhsM9KbT2s.jpg?scale-down-to=1024 1024w,https://framerusercontent.com/images/vEbdAy1Wk9UEYSYzDhsM9KbT2s.jpg 1040w\",style:{aspectRatio:\"1040 / 527\"},width:\"520\"}),/*#__PURE__*/e(\"p\",{children:/*#__PURE__*/e(\"span\",{style:{\"--framer-text-color\":\"rgb(128, 128, 128)\"},children:/*#__PURE__*/e(\"em\",{children:\"Figure 2: Finite-state machine for CSV files with a maximum record length.\"})})}),/*#__PURE__*/t(\"p\",{children:[\"The key difference from the FSM in figure 1 is that for each of the states\\xa0\",/*#__PURE__*/e(\"em\",{children:\"Q\"}),\",\\xa0\",/*#__PURE__*/e(\"em\",{children:\"AQ\"}),\",\\xa0\",/*#__PURE__*/e(\"em\",{children:\"AF\"}),\", and\\xa0\",/*#__PURE__*/e(\"em\",{children:\"UQ\"}),\"\\xa0there is one state per current record length, denoted by the number underneath the state name in figure 2. As you can see, only the newline symbol is valid when the record length is\\xa0\",/*#__PURE__*/e(\"em\",{children:\"N\"}),\". To avoid the problem caused by CSVs without quotes, we simply follow the paths from\\xa0\",/*#__PURE__*/e(\"em\",{children:\"Q1\"}),\"\\xa0and\\xa0\",/*#__PURE__*/e(\"em\",{children:\"AR0\"}),\"\\xa0in the modified FSM until one of them is rejected.\"]}),/*#__PURE__*/e(\"h2\",{children:\"Inherently ambiguous substrings\"}),/*#__PURE__*/e(\"p\",{children:\"I wish I could say that this was it, that implementing the logic above would give us a known starting point in all cases. Unfortunately, there are still corner cases where it fails. Here\u2019s a somewhat contrived example: Consider a CSV file that consists of a quote followed by newline over and over again, and imagine what would happen if you started reading in the middle of such a file. Say the first symbol you see is a newline: Is that newline is a record delimiter or embedded within a quoted field? Our paths from above go into infinite loops:\"}),/*#__PURE__*/t(\"p\",{children:[/*#__PURE__*/e(\"em\",{children:\"Q1 \u2192 Q2 \u2192 AQ3 \u2192 AR0 \u2192 Q1 \u2192\"}),\"\\xa0\u2026 and\\xa0\",/*#__PURE__*/e(\"em\",{children:\"AR0 \u2192 AR0 \u2192 Q1 \u2192 Q2 \u2192 AQ3 \u2192 AR0 \u2192 \u2026\"})]}),/*#__PURE__*/e(\"p\",{children:\"The good news is that these are really rare in real-world data sets. When they do occur, we can fail our processing\\xa0job and default back to not splitting the CSV file.\"}),/*#__PURE__*/e(\"h2\",{children:\"Conclusion\"}),/*#__PURE__*/e(\"p\",{children:\"By using this method of unambiguously locating the first record in a piece of a CSV file, we can take advantage of Hadoop\u2019s task distribution, and process big CSV files in parallel when possible. We can also determine if parallel processing is not possible and fall back to sequential processing.\"})]});export const richText10=/*#__PURE__*/t(r.Fragment,{children:[/*#__PURE__*/t(\"p\",{children:[/*#__PURE__*/e(n,{href:\"http://www.typescriptlang.org/\",nodeId:\"lP6yKOgUj\",openInNewTab:!0,smoothScroll:!1,children:/*#__PURE__*/e(\"a\",{children:\"Typescript\"})}),\"\\xa0has been getting significant attention in the past year and with over 2 million downloads per month on npm, there has undoubtedly been an increase in adoption. However, many people are still unsure if Typescript will benefit their project, and there are few resources that show how Typescript can be used in large projects and what the practical benefits are. In this post we aim to highlight how we use Typescript at Etleap so that people can get an impression of why we decided to use it and how we benefit from it.\"]}),/*#__PURE__*/e(\"h2\",{children:\"Why Typescript?\"}),/*#__PURE__*/e(\"p\",{children:\"At Etleap we use Typescript primarily to specify data structures. Javascript\u2019s dynamic typing and the ease of creating new objects make it very difficult to have an overview of all the different data structures that exist in an application as it grows. As a consequence, our developers spent a lot of time going back and forth in our documentation and codebase to remember property names and fix typos. Typescript lets your team have a shared, formal definition of the data structures in your application, and thereby remove ambiguity in the specifications. After introducing Typescript in our projects, we saw a reduction in time spent looking up the documentation, fixing typos, and a massive increase in the perceived robustness of our code. Previously we would have components that the developers were reluctant to touch, and that\u2019s not the case anymore.\"}),/*#__PURE__*/e(\"h2\",{children:\"Types\"}),/*#__PURE__*/e(\"p\",{children:\"Although the syntax is different, anyone familiar with strongly typed languages will find most of the concepts similar. There\u2019s an impressive feature set that allows expressing complex types, including generics, union types and string literal types. Here is an example of specifying an interface for a Tree in Typescript to showcase the possibilities:\"}),/*#__PURE__*/e(\"img\",{alt:\"undefined\",className:\"framer-image\",\"data-framer-asset\":\"data:framer/asset-reference,Ti5WITLj483cx5bCcLsOc47UKk.jpg?originalFilename=image_%25287%2529.jpg\",\"data-framer-height\":\"327\",\"data-framer-width\":\"309\",height:\"163\",src:\"https://framerusercontent.com/images/Ti5WITLj483cx5bCcLsOc47UKk.jpg\",style:{aspectRatio:\"309 / 327\"},width:\"154\"}),/*#__PURE__*/t(\"p\",{children:[\"An interesting aspect of Typescript is the way types are recognised. Essentially Typescript employs\\xa0\",/*#__PURE__*/e(n,{href:\"https://en.wikipedia.org/wiki/Duck_typing\",nodeId:\"lP6yKOgUj\",openInNewTab:!0,smoothScroll:!1,children:/*#__PURE__*/e(\"a\",{children:\"Duck typing\"})}),\"; if an object contains the required properties of an interface, it is recognised as such. This makes the type system very flexible, and makes a lot sense when working with Javascript.\"]}),/*#__PURE__*/e(\"img\",{alt:\"undefined\",className:\"framer-image\",\"data-framer-asset\":\"data:framer/asset-reference,pz8FjcLbkudLo17ypQc8CLOZjU.jpg?originalFilename=image.jpg\",\"data-framer-height\":\"149\",\"data-framer-width\":\"337\",height:\"74\",src:\"https://framerusercontent.com/images/pz8FjcLbkudLo17ypQc8CLOZjU.jpg\",style:{aspectRatio:\"337 / 149\"},width:\"168\"}),/*#__PURE__*/e(\"h2\",{children:\"Tooling benefits\"}),/*#__PURE__*/t(\"p\",{children:[\"The most useful features of Typescript require tooling support. All the common Javascript editors today provide out of the box or plugin support for Typescript. At Etleap, front-end engineers use\\xa0\",/*#__PURE__*/e(n,{href:\"https://code.visualstudio.com/\",nodeId:\"lP6yKOgUj\",openInNewTab:!0,smoothScroll:!1,children:/*#__PURE__*/e(\"a\",{children:\"VSCode\"})}),\", which has builtin support and is in fact written in Typescript.\"]}),/*#__PURE__*/e(\"h2\",{children:\"Auto complete and errors on incompatible types\"}),/*#__PURE__*/e(\"p\",{children:\"If you specify an interface for an object, you will be alerted if you do not conform to it. This could include misspelling a property name, forgetting a required property, or assigning the wrong type. You also get auto completion of property names, as well as information about the type and any Jsdoc associated with it.\"}),/*#__PURE__*/e(\"img\",{alt:\"undefined\",className:\"framer-image\",\"data-framer-asset\":\"data:framer/asset-reference,JGiBhfIl4lMPw2NwQWZT1O4lGII.png?originalFilename=Screenshot_2024-11-29_at_15.17.56.png\",\"data-framer-height\":\"162\",\"data-framer-width\":\"930\",height:\"81\",src:\"https://framerusercontent.com/images/JGiBhfIl4lMPw2NwQWZT1O4lGII.png\",srcSet:\"https://framerusercontent.com/images/JGiBhfIl4lMPw2NwQWZT1O4lGII.png?scale-down-to=512 512w,https://framerusercontent.com/images/JGiBhfIl4lMPw2NwQWZT1O4lGII.png 930w\",style:{aspectRatio:\"930 / 162\"},width:\"465\"}),/*#__PURE__*/e(\"h2\",{children:\"Specifying inputs for functions\"}),/*#__PURE__*/e(\"p\",{children:\"There are some editors that can provide many of the benefits of Typescript just by inference on regular Javascript. However, we found this functionality to be lacking, since it cannot infer types on function parameters. With Typescript it is easy to specify the type directly in the function declaration.\"}),/*#__PURE__*/e(\"img\",{alt:\"undefined\",className:\"framer-image\",\"data-framer-asset\":\"data:framer/asset-reference,YZx8h9k7o4tdHBG60o2rTJUABY.jpg?originalFilename=image_%25281%2529.jpg\",\"data-framer-height\":\"68\",\"data-framer-width\":\"451\",height:\"34\",src:\"https://framerusercontent.com/images/YZx8h9k7o4tdHBG60o2rTJUABY.jpg\",style:{aspectRatio:\"451 / 68\"},width:\"225\"}),/*#__PURE__*/e(\"h2\",{children:\"Typescript at Etleap\"}),/*#__PURE__*/e(\"h2\",{children:\"Specifying interfaces for our API\"}),/*#__PURE__*/e(\"p\",{children:\"One of our primary use cases is making interfaces to specify the data structures used in our REST API. We have a common format for each endpoint that specifies the parameters, request and response from the back end.\"}),/*#__PURE__*/e(\"img\",{alt:\"undefined\",className:\"framer-image\",\"data-framer-asset\":\"data:framer/asset-reference,TrmcCdlXZ711zzpD1tB6AWLR69Y.jpg?originalFilename=image_%25282%2529.jpg\",\"data-framer-height\":\"328\",\"data-framer-width\":\"361\",height:\"164\",src:\"https://framerusercontent.com/images/TrmcCdlXZ711zzpD1tB6AWLR69Y.jpg\",style:{aspectRatio:\"361 / 328\"},width:\"180\"}),/*#__PURE__*/e(\"p\",{children:\"This helps us keep a consistent documentation for our API that can be used by both front-end and back-end teams. We keep these interfaces in their own folder, separate from any implementation logic. It also makes it easy to work with the requests and responses, as we get auto-complete as well as inline and compile-time errors if the API is changed. This prevents common errors such as forgetting to update some part of the application to accommodate a change in the API.\"}),/*#__PURE__*/e(\"img\",{alt:\"undefined\",className:\"framer-image\",\"data-framer-asset\":\"data:framer/asset-reference,bL6KsQOFMzNMYcmgJQVtvBxV1I.png?originalFilename=Screenshot_2024-11-29_at_15.18.51.png\",\"data-framer-height\":\"164\",\"data-framer-width\":\"930\",height:\"82\",src:\"https://framerusercontent.com/images/bL6KsQOFMzNMYcmgJQVtvBxV1I.png\",srcSet:\"https://framerusercontent.com/images/bL6KsQOFMzNMYcmgJQVtvBxV1I.png?scale-down-to=512 512w,https://framerusercontent.com/images/bL6KsQOFMzNMYcmgJQVtvBxV1I.png 930w\",style:{aspectRatio:\"930 / 164\"},width:\"465\"}),/*#__PURE__*/e(\"h2\",{children:\"Specifying interfaces for React components\"}),/*#__PURE__*/t(\"p\",{children:[\"Typescript allows us to be more flexible about our component types than the\\xa0\",/*#__PURE__*/e(n,{href:\"https://facebook.github.io/react/docs/typechecking-with-proptypes.html\",nodeId:\"lP6yKOgUj\",openInNewTab:!0,smoothScroll:!1,children:/*#__PURE__*/e(\"a\",{children:\"propTypes React feature\"})}),\", and includes great tooling support. It\\xa0also makes it easy to reference commonly used data structures as part of the properties.\"]}),/*#__PURE__*/e(\"img\",{alt:\"undefined\",className:\"framer-image\",\"data-framer-asset\":\"data:framer/asset-reference,k2D5wXo3SheFhaU7jPeVsFYa0zs.jpg?originalFilename=image_%25283%2529.jpg\",\"data-framer-height\":\"274\",\"data-framer-width\":\"490\",height:\"137\",src:\"https://framerusercontent.com/images/k2D5wXo3SheFhaU7jPeVsFYa0zs.jpg\",style:{aspectRatio:\"490 / 274\"},width:\"245\"}),/*#__PURE__*/e(\"p\",{children:\"This has been a big win for us as it makes it a lot safer to edit React components that are used many places. Any changes to the properties of a component will result in errors anywhere the component is used with incompatible or missing properties. This also works beautifully with JSX.\"}),/*#__PURE__*/e(\"img\",{alt:\"undefined\",className:\"framer-image\",\"data-framer-asset\":\"data:framer/asset-reference,i2Ak4hJAkWJyub9JmpH2jyQMg2s.jpg?originalFilename=image_%25284%2529.jpg\",\"data-framer-height\":\"148\",\"data-framer-width\":\"446\",height:\"74\",src:\"https://framerusercontent.com/images/i2Ak4hJAkWJyub9JmpH2jyQMg2s.jpg\",style:{aspectRatio:\"446 / 148\"},width:\"223\"}),/*#__PURE__*/e(\"h2\",{children:\"You don\u2019t have to specify everything\"}),/*#__PURE__*/e(\"p\",{children:\"We are still writing Javascript, and we do not want Typescript to get in the way of that. One great thing about Typescript is that you can use it as much or as little as you want. If you only want to specify some types, or even no types, that is completely fine. This makes it easy and keeps the flexibility from the dynamic weak typing of Javascript, while still providing the benefits of a more rigid type system.\"}),/*#__PURE__*/e(\"h2\",{children:\"The bottom line\"}),/*#__PURE__*/e(\"p\",{children:\"We use Typescript extensively in our software, and the benefits we have seen are significant. If you are working on a medium to large project we would definitely recommend taking a second look at Typescript if you haven\u2019t yet pulled the trigger.\"})]});export const richText11=/*#__PURE__*/t(r.Fragment,{children:[/*#__PURE__*/t(\"p\",{children:[\"At Etleap, what we do is help customers\\xa0\",/*#__PURE__*/e(n,{href:\"https://en.wikipedia.org/wiki/Extract,_transform,_load\",nodeId:\"lP6yKOgUj\",openInNewTab:!0,smoothScroll:!1,children:/*#__PURE__*/e(\"a\",{children:\"ETL\"})}),\"\\xa0their data. Our customers consume data from many different services like Salesforce, Marketo and Google AdWords, but the vast majority of them also have data in traditional SQL databases.\"]}),/*#__PURE__*/e(\"img\",{alt:\"undefined\",className:\"framer-image\",\"data-framer-asset\":\"data:framer/asset-reference,TreUmTMZQYsRkR21tFxUVfMSaM.jpg?originalFilename=image_%25283%2529.jpg\",\"data-framer-height\":\"768\",\"data-framer-width\":\"1024\",height:\"384\",src:\"https://framerusercontent.com/images/TreUmTMZQYsRkR21tFxUVfMSaM.jpg\",srcSet:\"https://framerusercontent.com/images/TreUmTMZQYsRkR21tFxUVfMSaM.jpg?scale-down-to=512 512w,https://framerusercontent.com/images/TreUmTMZQYsRkR21tFxUVfMSaM.jpg 1024w\",style:{aspectRatio:\"1024 / 768\"},width:\"512\"}),/*#__PURE__*/e(\"p\",{children:/*#__PURE__*/e(\"span\",{style:{\"--framer-text-color\":\"rgb(128, 128, 128)\"},children:/*#__PURE__*/e(\"em\",{children:\"From a recent company offsite: drinking coldbrew next to a coffee grinder that looks like a fire hydrant is another thing that we do.\"})})}),/*#__PURE__*/t(\"p\",{children:[\"There are many strategies for data warehousing and it can be confusing with so many buzzwords flying around (beware of your data lake collapsing into singularity due to\\xa0\",/*#__PURE__*/e(n,{href:\"https://datagravity.org/\",nodeId:\"lP6yKOgUj\",openInNewTab:!0,smoothScroll:!1,children:/*#__PURE__*/e(\"a\",{children:\"data gravity\"})}),\"!). Increasingly we see that many customers are settling on aggregating all their data into\\xa0\",/*#__PURE__*/e(n,{href:\"https://aws.amazon.com/redshift/\",nodeId:\"lP6yKOgUj\",openInNewTab:!0,smoothScroll:!1,children:/*#__PURE__*/e(\"a\",{children:\"Amazon Redshift\"})}),\".\"]}),/*#__PURE__*/e(\"p\",{children:\"As you can see, Etleap needs to connect to customers\u2019 SQL databases on both ends of the data pipeline. There are many things that we do to ensure your data moves from one side to the other securely and consistently, and we will write about them on this blog. Today I am going to write about database connection leaks.\"}),/*#__PURE__*/t(\"p\",{children:[\"Database connection is a precious resource. Each Redshift cluster only allows\\xa0\",/*#__PURE__*/e(n,{href:\"http://docs.aws.amazon.com/redshift/latest/mgmt/amazon-redshift-limits.html\",nodeId:\"lP6yKOgUj\",openInNewTab:!0,smoothScroll:!1,children:/*#__PURE__*/e(\"a\",{children:\"500 concurrent connections\"})}),\", for example. If we don\u2019t manage our connections carefully, it will directly affect our customers\u2019 ability to query their data.\"]}),/*#__PURE__*/t(\"p\",{children:[\"Since we are mostly a Java shop, we use JDBC extensively, along with\\xa0\",/*#__PURE__*/e(n,{href:\"https://github.com/brettwooldridge/HikariCP\",nodeId:\"lP6yKOgUj\",openInNewTab:!0,smoothScroll:!1,children:/*#__PURE__*/e(\"a\",{children:\"HikariCP\"})}),\"\\xa0to manage our database connections. Our first line of defense in preventing leaks is Java 7\u2019s\\xa0\",/*#__PURE__*/e(n,{href:\"https://docs.oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.html\",nodeId:\"lP6yKOgUj\",openInNewTab:!0,smoothScroll:!1,children:/*#__PURE__*/e(\"a\",{children:\"try-with-resources\"})}),\", which means we don\u2019t have to remember to explicitly close connections:\"]}),/*#__PURE__*/e(\"p\",{children:/*#__PURE__*/e(\"strong\",{children:\"1try (Connection connection = dataSource.getConnection()) { \"})}),/*#__PURE__*/e(\"p\",{children:/*#__PURE__*/e(\"strong\",{children:\"2// do something with the connection\"})}),/*#__PURE__*/e(\"p\",{children:/*#__PURE__*/e(\"strong\",{children:\" 3}\"})}),/*#__PURE__*/e(\"p\",{children:/*#__PURE__*/e(\"br\",{className:\"trailing-break\"})}),/*#__PURE__*/e(\"p\",{children:\"This obviously only helps if we remember to use try-with-resources in the first place. Also, sometimes it\u2019s not trivial to structure the code so that the connection can be opened and closed in the same block. Fortunately, HikariCP has a built-in connection leak detection mechanism, which is based on timeouts:\"}),/*#__PURE__*/e(\"p\",{children:/*#__PURE__*/e(\"strong\",{children:\"1HikariConfig hikariConfig = new HikariConfig(); 2hikariConfig.setLeakDetectionThreshold(60*1000);\"})}),/*#__PURE__*/e(\"p\",{children:\"And if a leak is detected, we will see this in the log:\"}),/*#__PURE__*/t(\"p\",{children:[/*#__PURE__*/e(\"strong\",{children:\"11:28:50.535 [warn] [Hikari Housekeeping Timer (pool HikariCP Pool 1 (small) example \"}),/*#__PURE__*/e(n,{href:\"http://example.com/\",nodeId:\"lP6yKOgUj\",openInNewTab:!0,smoothScroll:!1,children:/*#__PURE__*/e(\"a\",{children:/*#__PURE__*/e(\"strong\",{children:\"example.com\"})})}),/*#__PURE__*/e(\"strong\",{children:\" 3306)] \"})]}),/*#__PURE__*/e(\"p\",{children:/*#__PURE__*/e(\"strong\",{children:\"1com.zaxxer.hikari.pool.LeakTask - [p:] Connection leak detection triggered for connection Connection@6494a9b8,  \"})}),/*#__PURE__*/e(\"p\",{children:/*#__PURE__*/e(\"strong\",{children:\"2stack trace follows\"})}),/*#__PURE__*/e(\"p\",{children:/*#__PURE__*/e(\"strong\",{children:\"3java.lang.Exception: Apparent connection leak detected\"})}),/*#__PURE__*/e(\"p\",{children:/*#__PURE__*/e(\"strong\",{children:\"4at service.JDBCConnectionService.getConnection(JDBCConnectionService.java) ~[classes/:na]\"})}),/*#__PURE__*/e(\"p\",{children:/*#__PURE__*/e(\"strong\",{children:\" ...\"})}),/*#__PURE__*/e(\"p\",{children:/*#__PURE__*/e(\"br\",{className:\"trailing-break\"})}),/*#__PURE__*/e(\"p\",{children:\"From HikariCP\u2019s wiki:\"}),/*#__PURE__*/e(\"p\",{children:/*#__PURE__*/e(\"em\",{children:\"\u231AleakDetectionThreshold\"})}),/*#__PURE__*/e(\"p\",{children:/*#__PURE__*/e(\"em\",{children:\"This property controls the amount of time that a connection can be out of the pool before a message is logged indicating a possible connection leak. A value of 0 means leak detection is disabled. Lowest acceptable value for enabling leak detection is 2000 (2 secs). Default: 0\"})}),/*#__PURE__*/e(\"p\",{children:/*#__PURE__*/e(\"br\",{className:\"trailing-break\"})}),/*#__PURE__*/t(\"p\",{children:[\"Leak detection is disabled by default because incorrect timeout values would lead to frequent false positives. There are many methods and techniques for minimizing Redshift loading time (Redshift\u2019s\\xa0\",/*#__PURE__*/e(n,{href:\"http://docs.aws.amazon.com/redshift/latest/dg/c_loading-data-best-practices.html\",nodeId:\"lP6yKOgUj\",openInNewTab:!0,smoothScroll:!1,children:/*#__PURE__*/e(\"a\",{children:\"Best Practices for Loading Data\"})}),\"\\xa0is a good starting point), but loading large amounts of data can still take a very long time. Contention with other database queries can also make it tricky to determine what the correct timeout should be. At Etleap, we set it to a moderately high value and live with occasional false positives.\"]}),/*#__PURE__*/t(\"p\",{children:[\"Our third line of defense is\\xa0\",/*#__PURE__*/e(n,{href:\"https://github.com/maginatics/jdbclint\",nodeId:\"lP6yKOgUj\",openInNewTab:!0,smoothScroll:!1,children:/*#__PURE__*/e(\"a\",{children:\"JDBC lint\"})}),\". It has an extensive list of checks that it can perform but we only use it for connection leak detection:\"]}),/*#__PURE__*/e(i.div,{className:\"framer-text-module\",style:{height:\"auto\",width:\"100%\"},children:/*#__PURE__*/e(a,{componentIdentifier:\"module:pVk4QsoHxASnVtUBp6jr/QVzZltTawVJTjmjAWG3C/CodeBlock.js:default\",children:t=>/*#__PURE__*/e(o,{...t,code:\"<strong>1Configuration jdbcLintConfig = new Configuration(EnumSet.of(Check.CONNECTION_MISSING_CLOSE),\\n2Arrays.asList(Configuration.PRINT_STACK_TRACE_ACTION));\\n3connection = ConnectionProxy.newInstance(connection, jdbcLintConfig);</strong>\",language:\"JSX\"})})}),/*#__PURE__*/e(\"p\",{children:/*#__PURE__*/e(\"br\",{className:\"trailing-break\"})}),/*#__PURE__*/t(\"p\",{children:[\"Under the hood, it works by checking if\\xa0\",/*#__PURE__*/e(\"code\",{children:\"close()\"}),\"\\xa0has been called at object finalization time. So if the connection is leaked, and Java\u2019s GC kicks in, and object finalization is performed, we will see this in the log:\"]}),/*#__PURE__*/e(i.div,{className:\"framer-text-module\",style:{height:\"auto\",width:\"100%\"},children:/*#__PURE__*/e(a,{componentIdentifier:\"module:pVk4QsoHxASnVtUBp6jr/QVzZltTawVJTjmjAWG3C/CodeBlock.js:default\",children:t=>/*#__PURE__*/e(o,{...t,code:\"<strong>1java.sql.SQLException: java.sql.SQLException\\n2at com.maginatics.jdbclint.Configuration$1.apply(Configuration.java:71)\\n3at com.maginatics.jdbclint.Utils.fail(Utils.java:30)\\n4at com.maginatics.jdbclint.ConnectionProxy.finalize(ConnectionProxy.java:128)\\n5...\\n6Caused by: java.sql.SQLException\\n7at com.maginatics.jdbclint.ConnectionProxy.(ConnectionProxy.java:40)\\n8at com.maginatics.jdbclint.ConnectionProxy.newInstance(ConnectionProxy.java:60)\\n9at service.JDBCConnectionService.getConnection(JDBCConnectionService.java) ~[classes/:na]\\n10...</strong>\",language:\"JSX\"})})}),/*#__PURE__*/e(\"p\",{children:\"Unlike HikariCP\u2019s leak detection which can have false positives, JDBC lint doesn\u2019t detect all the errors so it can have false negatives. In particular, there\u2019s no guarantee that a particular leaked object is garbage collected. And after an object is GC\u2019ed, when or even if finalizer is run is also JVM implementation dependent. One way to verify that our setup is working by inducing a leak ourselves and coercing the JVM to do the right thing:\"}),/*#__PURE__*/e(i.div,{className:\"framer-text-module\",style:{height:\"auto\",width:\"100%\"},children:/*#__PURE__*/e(a,{componentIdentifier:\"module:pVk4QsoHxASnVtUBp6jr/QVzZltTawVJTjmjAWG3C/CodeBlock.js:default\",children:t=>/*#__PURE__*/e(o,{...t,code:\"<strong>1connectionService.getConnection(); // leak\\n2System.gc();\\n3System.runFinalization();</strong>\",language:\"JSX\"})})}),/*#__PURE__*/e(\"p\",{children:\"So in a nutshell, we ensure we don\u2019t leak database connections by:\"}),/*#__PURE__*/t(\"ol\",{children:[/*#__PURE__*/e(\"li\",{\"data-preset-tag\":\"p\",children:/*#__PURE__*/e(\"p\",{children:\"Adopting good coding practice (try-with-resources)\"})}),/*#__PURE__*/e(\"li\",{\"data-preset-tag\":\"p\",children:/*#__PURE__*/e(\"p\",{children:\"Investing in layered defenses to ensure that we can detect a bug if we have one. Both HikariCP\u2019s and JDBC lint\u2019s leak detection are running 100% of the time in our production environment.\"})})]}),/*#__PURE__*/e(\"p\",{children:\"ORM frameworks often abstract away database connection handling from application code, however it\u2019s not well suited for ETL style workloads where we connect to databases with schemas that we have no control over. There are other times when ORM is undesirable, but as this post shows we don\u2019t have to sacrifice all the safety mechanisms of an ORM framework even when we have to roll our own SQL.\"})]});\nexport const __FramerMetadata__ = {\"exports\":{\"richText7\":{\"type\":\"variable\",\"annotations\":{\"framerContractVersion\":\"1\"}},\"richText6\":{\"type\":\"variable\",\"annotations\":{\"framerContractVersion\":\"1\"}},\"richText2\":{\"type\":\"variable\",\"annotations\":{\"framerContractVersion\":\"1\"}},\"richText10\":{\"type\":\"variable\",\"annotations\":{\"framerContractVersion\":\"1\"}},\"richText\":{\"type\":\"variable\",\"annotations\":{\"framerContractVersion\":\"1\"}},\"richText5\":{\"type\":\"variable\",\"annotations\":{\"framerContractVersion\":\"1\"}},\"richText3\":{\"type\":\"variable\",\"annotations\":{\"framerContractVersion\":\"1\"}},\"richText11\":{\"type\":\"variable\",\"annotations\":{\"framerContractVersion\":\"1\"}},\"richText4\":{\"type\":\"variable\",\"annotations\":{\"framerContractVersion\":\"1\"}},\"richText9\":{\"type\":\"variable\",\"annotations\":{\"framerContractVersion\":\"1\"}},\"richText1\":{\"type\":\"variable\",\"annotations\":{\"framerContractVersion\":\"1\"}},\"richText8\":{\"type\":\"variable\",\"annotations\":{\"framerContractVersion\":\"1\"}},\"__FramerMetadata__\":{\"type\":\"variable\"}}}"],
  "mappings": "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",
  "names": ["richText", "u", "x", "p", "Link", "richText1", "richText2", "richText3", "richText4", "richText5", "richText6", "motion", "ComponentPresetsConsumer", "t", "CodeBlock_default", "richText7", "richText8", "richText9", "richText10", "richText11", "__FramerMetadata__"]
}
