<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Gwen (Chen) Shapira</title>
    <description>The latest articles on DEV Community by Gwen (Chen) Shapira (@gwenshap).</description>
    <link>https://dev.to/gwenshap</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F136599%2Ffc58c575-8436-4255-b74c-1c06df869c71.jpg</url>
      <title>DEV Community: Gwen (Chen) Shapira</title>
      <link>https://dev.to/gwenshap</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/gwenshap"/>
    <language>en</language>
    <item>
      <title>We keep shipping!!!</title>
      <dc:creator>Gwen (Chen) Shapira</dc:creator>
      <pubDate>Fri, 11 Apr 2025 19:29:24 +0000</pubDate>
      <link>https://dev.to/gwenshap/we-keep-shipping-5b95</link>
      <guid>https://dev.to/gwenshap/we-keep-shipping-5b95</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/sriramsub/we-just-shipped-nile-auth-v40-account-linking-cors-support-and-more-1ci6" class="crayons-story__hidden-navigation-link"&gt;🚀 We just shipped Nile-Auth v4.0: Account Linking, CORS Support, and More&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/sriramsub" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F794061%2F4e36c8a3-7951-4ba6-b0b2-f777ba1e9d0a.jpeg" alt="sriramsub profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/sriramsub" class="crayons-story__secondary fw-medium m:hidden"&gt;
              sriramsub
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                sriramsub
                
              
              &lt;div id="story-author-preview-content-2400467" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/sriramsub" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F794061%2F4e36c8a3-7951-4ba6-b0b2-f777ba1e9d0a.jpeg" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;sriramsub&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/sriramsub/we-just-shipped-nile-auth-v40-account-linking-cors-support-and-more-1ci6" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Apr 11 '25&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/sriramsub/we-just-shipped-nile-auth-v40-account-linking-cors-support-and-more-1ci6" id="article-link-2400467"&gt;
          🚀 We just shipped Nile-Auth v4.0: Account Linking, CORS Support, and More
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/b2b"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;b2b&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/postgres"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;postgres&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/authjs"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;authjs&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/nextjs"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;nextjs&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/sriramsub/we-just-shipped-nile-auth-v40-account-linking-cors-support-and-more-1ci6" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/raised-hands-74b2099fd66a39f2d7eed9305ee0f4553df0eb7b4f11b01b6b1b499973048fe5.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/multi-unicorn-b44d6f8c23cdd00964192bedc38af3e82463978aa611b4365bd33a0f1f4f3e97.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;2&lt;span class="hidden s:inline"&gt; reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/sriramsub/we-just-shipped-nile-auth-v40-account-linking-cors-support-and-more-1ci6#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              Comments


              &lt;span class="hidden s:inline"&gt;Add Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            1 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success"&gt;
                

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

&lt;/div&gt;


</description>
      <category>b2b</category>
      <category>postgres</category>
      <category>authjs</category>
      <category>nextjs</category>
    </item>
    <item>
      <title>[Boost]</title>
      <dc:creator>Gwen (Chen) Shapira</dc:creator>
      <pubDate>Fri, 28 Mar 2025 13:58:46 +0000</pubDate>
      <link>https://dev.to/gwenshap/-1clb</link>
      <guid>https://dev.to/gwenshap/-1clb</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/sriramsub/introducing-nile-auth-for-b2b-apps-5jm" class="crayons-story__hidden-navigation-link"&gt;Introducing Nile Auth for B2B apps&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/sriramsub" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F794061%2F4e36c8a3-7951-4ba6-b0b2-f777ba1e9d0a.jpeg" alt="sriramsub profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/sriramsub" class="crayons-story__secondary fw-medium m:hidden"&gt;
              sriramsub
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                sriramsub
                
              
              &lt;div id="story-author-preview-content-2363070" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/sriramsub" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F794061%2F4e36c8a3-7951-4ba6-b0b2-f777ba1e9d0a.jpeg" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;sriramsub&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/sriramsub/introducing-nile-auth-for-b2b-apps-5jm" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Mar 28 '25&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/sriramsub/introducing-nile-auth-for-b2b-apps-5jm" id="article-link-2363070"&gt;
          Introducing Nile Auth for B2B apps
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/b2b"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;b2b&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/postgres"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;postgres&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/authjs"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;authjs&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/nextjs"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;nextjs&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/sriramsub/introducing-nile-auth-for-b2b-apps-5jm" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/exploding-head-daceb38d627e6ae9b730f36a1e390fca556a4289d5a41abb2c35068ad3e2c4b5.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/multi-unicorn-b44d6f8c23cdd00964192bedc38af3e82463978aa611b4365bd33a0f1f4f3e97.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;5&lt;span class="hidden s:inline"&gt; reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/sriramsub/introducing-nile-auth-for-b2b-apps-5jm#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              Comments


              &lt;span class="hidden s:inline"&gt;Add Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            7 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success"&gt;
                

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

&lt;/div&gt;


</description>
      <category>b2b</category>
      <category>postgres</category>
      <category>webdev</category>
      <category>authjs</category>
    </item>
    <item>
      <title>Debunking 6 common pgvector myths</title>
      <dc:creator>Gwen (Chen) Shapira</dc:creator>
      <pubDate>Tue, 01 Oct 2024 00:43:17 +0000</pubDate>
      <link>https://dev.to/gwenshap/debunking-6-common-pgvector-myths-knh</link>
      <guid>https://dev.to/gwenshap/debunking-6-common-pgvector-myths-knh</guid>
      <description>&lt;p&gt;Pgvector is Postgres’ highly popular extension for storing, indexing and querying vectors. Vectors have been a useful data type for a long time, but recently they’ve seen rise in popularity due to their usefulness in RAG (Retrieval Augmented Generation) architectures of AI-based applications. Vectors typically power the retrieval part - using vector similarity search and nearest-neighbor algorithms, one can find the most relevant documents for a given user question. &lt;/p&gt;

&lt;p&gt;Having the ability to store vectors in your normal relational database, as opposed to a dedicated vector store, means that you can use all the normal relational database capabilities together with vector search - join vector tables with other data and metadata, use additional fields for filtering, retrieve related information and so on.&lt;/p&gt;

&lt;p&gt;From conversations in the pg_vector community, it became clear that there are some common misconceptions and misunderstandings around its best practices and use. As a result of these misunderstandings, some people avoid pg_vector completely or use it less effectively than they otherwise would. So, let's fix this!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnp4ohz0ybdh0rxr1s6g0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnp4ohz0ybdh0rxr1s6g0.png" alt="Image of Mark Twain with his quote: "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Myth 1: You always need to use a vector index
&lt;/h2&gt;

&lt;p&gt;This myth is a result of certain vector stores and popular libraries using the term "index" to describe any method of storing vectors. This has led to the misconception that indexes are the only way to store vectors.&lt;/p&gt;

&lt;p&gt;This isn’t true in Postgres terminology. The trick is that other vector stores have what they call a “flat” index. Flat index basically means “no hierarchy”. In postgres, the default table structure is flat. So if you just create a table with a column of vector type, insert some vectors and don’t create any indexes, you actually have what is called elsewhere a flat vector index.&lt;/p&gt;

&lt;p&gt;Now that we know that you technically don’t need to create a vector index in order to use pg_vector, you still need to decide when to use an index, which one to use and when to use it.&lt;/p&gt;

&lt;p&gt;Let’s take as an example an application that embeds transcripts of sales conversations for searches and knowledge extraction. You may have 10M embeddings in your database, but each one of your customers will have under 10,000. And each sales person has under 1000. And maybe they typically only search calls from the last few weeks, so it is actually under 100.&lt;/p&gt;

&lt;p&gt;If you usually only need to search 100 or 1000 vectors, you are almost certainly better off without any vector index. Instead, you can use normal b-tree indexes (maybe with partitions) to limit the query to scan just the right subset of vectors. This means you will have full recall (the indexes perform approximate nearest neighbor search, so there could be loss of recall) and can save on the time, memory and CPU of maintaining indexes that are unlikely to help you (and that the Postgres planner may rightly decide not to use).&lt;/p&gt;

&lt;h2&gt;
  
  
  Myth 2: Vector indexes semantics are similar to other indexes
&lt;/h2&gt;

&lt;p&gt;If you are familiar with indexes in relational databases, but less familiar with vector indexes, the last few paragraphs may have been very confusing. What do I mean by trading off performance vs recall? &lt;/p&gt;

&lt;p&gt;Typically a query that uses the index will return the exact same data that will be returned by a query that doesn’t use the index. This is basic SQL/relational semantics and is expected to be guaranteed for every index and every query. This expectation is so ingrained that most of us aren’t even aware that we expect it.&lt;/p&gt;

&lt;p&gt;But vector indexes are not like that. They are data structures for efficient approximate nearest neighbor search (ANN). They improve performance by limiting the search for nearest neighbors to specific subsets of the graph. These subsets are selected because they are likely to contain the nearest neighbors, but not guaranteed.&lt;/p&gt;

&lt;p&gt;This also gives you a hint on how the performance / recall tradeoff works - the more graph subsets you search, the more likely you are to find the actual nearest neighbors, but the longer it takes. In addition, different types of vector indexes give you additional configuration options  - how many subsets do you split the collection into? How exhaustively you “map” each subset? These decisions will also impact the performance / recall tradeoff of the index. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/pgvector/pgvector?tab=readme-ov-file#indexing" rel="noopener noreferrer"&gt;PG Vector documentation&lt;/a&gt; explains the types of indexes and the different parameters you can configure when creating and querying them. Definitely worth reading in detail and experimenting with them. &lt;/p&gt;

&lt;h2&gt;
  
  
  Myth 3: You can’t store more than 2000 dimensions in a vector index
&lt;/h2&gt;

&lt;p&gt;At the root of this myth is the simple fact that Postgres blocks are limited to 8K in size. By default, vectors are a collection of floats, and each float is 32bit. A simple math shows that if you take overheads into consideration, at around 2000 dimensions, you get very close to the 8K limit. You can still store the data, &lt;a href="https://wiki.postgresql.org/wiki/TOAST" rel="noopener noreferrer"&gt;Postgres has a TOAST feature&lt;/a&gt; which uses “pointers” to store a row in more than one block. But - you can’t build a vector index if the rows are split using TOAST.&lt;/p&gt;

&lt;p&gt;One option is to use embedding models that output vectors with fewer dimensions, or a model that has been &lt;a href="https://www.nomic.ai/blog/posts/nomic-embed-matryoshka" rel="noopener noreferrer"&gt;trained to “scale down” without losing performance&lt;/a&gt;. But, what if you have an embedding model that works really well for your data and has more dimensions? Switching to a different model may be completely unacceptable.&lt;/p&gt;

&lt;p&gt;Another option is to use feature extraction algorithms that reduce dimensions of vectors from other models while attempting to preserve accuracy. PCA, t-SNE, and UMAP are relatively well known for this, and there are &lt;a href="https://arxiv.org/abs/1708.03629" rel="noopener noreferrer"&gt;some results that show they work quite well&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;However a much simpler approach is to use quantization. Quantization is the process of using smaller data type for each dimension . PG_vector supports &lt;code&gt;half_vec&lt;/code&gt; type with scalar quantization. It converts the floats to 16bit type by removing the least significant digits. This makes sense - we typically use vectors and indexes for nearest neighbor search. These insignificant digits typically don’t have much impact on the relative distances between vectors.  &lt;/p&gt;

&lt;p&gt;Since &lt;code&gt;half_vec&lt;/code&gt; takes half the size of the usual float, you can store 4000-ish dimensions of &lt;code&gt;half_vec&lt;/code&gt; type. Looking ahead, the community is also iterating on 8bit quantization of embeddings with an int_vec type which will allow storing 8000-ish dimensions. &lt;/p&gt;

&lt;p&gt;Even if you have smaller embeddings that already fit into a Postgres block and can be indexed, storing half the data will greatly improve performance and reduce resource utilization. &lt;a href="https://jkatz05.com/post/postgres/pgvector-scalar-binary-quantization/" rel="noopener noreferrer"&gt;All with almost no impact on recall&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Myth 4: Using vector index with other filters will miss data
&lt;/h2&gt;

&lt;p&gt;This isn’t quite as myth-y as the others. In fact, at the time of writing, this is still true. But in the upcoming pg_vector release, 0.8.0, we will be able to relegate this to a myth.&lt;br&gt;
So what does “use vector index with other filters” mean? &lt;/p&gt;

&lt;p&gt;Imagine that you indexed your company wiki, and now you want to find the documents most similar to “promotion process and policy”. But since your company has several business units with their own policies, you want to search only within the “engineering” category. Your query will look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="n"&gt;doc_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;doc_title&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;document_embeddings&lt;/span&gt; 
&lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;embedding&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&amp;gt;&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="k"&gt;and&lt;/span&gt; &lt;span class="n"&gt;doc_category&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'engineering'&lt;/span&gt;
&lt;span class="k"&gt;order&lt;/span&gt; &lt;span class="k"&gt;by&lt;/span&gt; &lt;span class="n"&gt;embedding&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&amp;gt;&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="k"&gt;limit&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;How can Postgres execute such a query?&lt;/p&gt;

&lt;p&gt;While we may want it to search only a subset of the vector index that belongs to ‘engineering’ category, unless you previously created partitions or partial indexes, such a subset will not exist.&lt;/p&gt;

&lt;p&gt;What happens is that Postgres uses the vector index first, finds the 10 nearest neighbors, and then filters them and throws out anything that isn’t in the engineering category. The problem, of course, is that this may result in anything from 10 to 0 rows. And we wanted to show 10 rows in our search results. This is the problem - we want K nearest neighbors after filtering, but we can’t know in advance how many neighbors we need the index to return in order to achieve this.&lt;/p&gt;

&lt;p&gt;Version 0.8.0 will introduce iterative vector indexes. This will allow Postgres to scan the index, find nearest neighbors, apply the filter, scan the index a bit more, filter more… and continue until the desired number of neighbors is found and can be returned.&lt;/p&gt;

&lt;p&gt;Version 0.8.0 will also include an improvement to the cost estimate of using vector indexes. This will help Postgres decide when to use the vector index and when to rely on just the B-Tree or GiST indexes. Both these improvements together will make it much easier to create indexes and run queries, knowing that Postgres will do the right thing with them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Myth 5: Vector similarity is only useful for RAG
&lt;/h2&gt;

&lt;p&gt;Vector embeddings are becoming increasingly popular due to their role in Retrieval-Augmented Generation (RAG). In RAG, embeddings help locate and retrieve relevant context, which allows large language models (LLMs) to answer questions more accurately and reduce the risk of hallucination.&lt;/p&gt;

&lt;p&gt;But sometimes it looks like we forgot all the other uses of vector embeddings. Vectors help find semantically similar items. Finding similar items is useful even without the LLM. For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Support&lt;/strong&gt;: Find knowledge base articles that are relevant to a support ticket and suggest them to the customer or support agent.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Issue tracking&lt;/strong&gt;: Detect duplicate reports of the same issue.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Recommendations&lt;/strong&gt;: Recommend items that are similar to ones that the customer already liked. “If you enjoyed this book, you’ll probably also like…”&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Anomaly detection&lt;/strong&gt;: instead of finding the most similar items, we can use vector distance to detect when a new item has no nearest neighbors. If it is very far from every existing item, it is an anomaly and can be reported.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shop for similar items&lt;/strong&gt;: Given a photo of a product, you can search for the most similar products.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In all these cases, just finding the nearest neighbors is enough, there is no need for an LLM in the loop.&lt;/p&gt;

&lt;h2&gt;
  
  
  Myth 6: pg_vector does not support BM25 (and other sparse vectors)
&lt;/h2&gt;

&lt;p&gt;There are two types of vector embeddings: Dense and Sparse.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Dense vectors&lt;/strong&gt; are typically generated by trained language models and they encode the semantic meaning behind a sentence or a document. This representation is a bit opaque, in the sense that you cannot map each dimension to a specific word or a concept. Dense vectors typically have 256-4096 dimensions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sparse vectors&lt;/strong&gt; are typically the result of traditional text search algorithms (TF-IDF, BM25, SPLADE) that use vectors to represent information about the importance of words used in each text. In sparse vectors, each dimension represents a word and the value indicates how common / important the word is in each text. The number of dimensions in sparse vectors depends on either the number of distinct words in the dataset (TF-IDF, BM25) or the number of words the model was trained on (30,522 in case of SPLADE). Since most texts only contain a small subset of all words, when using sparse vectors most of the dimensions have the value 0. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/pgvector/pgvector?tab=readme-ov-file#sparse-vectors" rel="noopener noreferrer"&gt;In version 0.7.0, pg_vector added support for sparse vectors&lt;/a&gt; with the &lt;code&gt;sparsevec&lt;/code&gt; type. This type only stores the non-zero elements of the vector. You insert sparse vectors by specifying only the non-zero values and their indexes. If you use pg_vector client libraries (they have libraries for many languages and ORMs), you’ll use their sparse vector type, which automatically convert vectors to the correct text representation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Vector indexes behave a bit differently than typical indexes in relational databases. In addition, the domain of vector embeddings has its own terminology, which isn’t always clear for new arrivals. &lt;/p&gt;

&lt;p&gt;When I first started working with vector embeddings and pg_vector, I found many of the topics above confusing. &lt;a href="https://www.thenile.dev/" rel="noopener noreferrer"&gt;Nile&lt;/a&gt; has &lt;a href="https://www.thenile.dev/docs/ai-embeddings/pg_vector" rel="noopener noreferrer"&gt;supported pgvector&lt;/a&gt; since our first private beta release, about a year ago. From my interactions with our users and the pgvector community, I’ve seen others face similar challenges. I hope this blog will be helpful, and I believe almost everyone will walk away with at least one new insight. &lt;/p&gt;

&lt;p&gt;Perhaps the most important lesson is that pg_vector is constantly evolving. Version 0.7.0 was released in April this year, version 0.8.0 is anticipated for October. Each vector adds more functionality and resolves old limitations. So it is important to revisit our assumptions and refresh our knowledge on a regular basis. Remember: It isn’t what you don’t know that gets you, its what you think you know but is no longer true.&lt;/p&gt;

</description>
      <category>postgres</category>
      <category>vectordatabase</category>
      <category>ai</category>
      <category>rag</category>
    </item>
    <item>
      <title>Investigating 15s HTTP response time in AWS ECS</title>
      <dc:creator>Gwen (Chen) Shapira</dc:creator>
      <pubDate>Sat, 16 Jul 2022 02:53:24 +0000</pubDate>
      <link>https://dev.to/gwenshap/investigating-15s-http-response-time-in-aws-ecs-2gge</link>
      <guid>https://dev.to/gwenshap/investigating-15s-http-response-time-in-aws-ecs-2gge</guid>
      <description>&lt;p&gt;It started innocently enough. During standup, someone mentioned “my branch deployment is a bit slow due to cold-start problem, but no big deal”. Indeed, cold-start should have no impact on production where the environment is long running. No big deal.&lt;/p&gt;

&lt;p&gt;But later the same day, I tried checking something in a test system and noticed that a lot of my requests were taking very long time. In fact, I couldn’t get anything done. Since this environment was also long running, it could not be cold start problem.&lt;/p&gt;

&lt;p&gt;Cloudwatch metrics on the load balancer showed that all requests return in 6ms or less and that they are all successful. My home internet seemed fine. Time to get an external view. &lt;/p&gt;

&lt;p&gt;I set up Pingdom to get our &lt;code&gt;/healthcheck&lt;/code&gt; every minute and the pattern became clear: 50% of the response times were around 300ms. &lt;strong&gt;The other 50% took over 15s.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnuucdmasao669qrl086g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnuucdmasao669qrl086g.png" alt="Screenshot of request log showing the latencies"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Up until this point, my mental model of our deployment was something like this (we have more stuff, but it isn’t relevant here):&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1bar0jg5pycijsscjvjj.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1bar0jg5pycijsscjvjj.jpg" alt="Architecture diagram with one LB connecting to one ECS task"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I ran Wireshark on my machine and ran a bunch of test requests. The 50% that were successful talked to one IP and the other 50% talked to another IP. &lt;/p&gt;

&lt;p&gt;Looking at our configuration, I discovered that our ALB has two availability zones and cross-zone failover.  This is mandatory for ECS tasks. So now I had a better mental model:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F075s3l1rks1yf57e6gfp.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F075s3l1rks1yf57e6gfp.jpg" alt="Architecture diagram with two LB in two zones connecting to one ECS task"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Note that we have LB in both zones, but ECS task in just on of them. An AWS documentation page that I can no longer find said something like “for best performance, run ECS task in every zone that has a load balancer”.  I was naive enough to believe that I found the issue and went ahead to set up the extra tasks.&lt;/p&gt;

&lt;p&gt;It took longer than I would have liked because zone 2d didn’t have the EC2 machine type that my task required, so I had to re-configure everything around a new machine type or move everything around zones, which meant learning more about ASG and ECS. But finally I had the following configuration:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzsyc9acvvjq7g5kbzk3j.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzsyc9acvvjq7g5kbzk3j.jpg" alt="Architecture diagram with two LB in two zones connecting to two ECS tasks in two zones"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Unfortunately, the performance issue remained. Damn it. &lt;/p&gt;

&lt;p&gt;I went back to the original system, but investigated the network configuration in more detail. One detail that I discarded until then was that few days earlier, after a long investigation and much debates, we added a NAT to our system. &lt;/p&gt;

&lt;p&gt;Why NAT? Because our ECS task occasionally had to make an outbound call to a public service (Google’s identity platform) and our investigation revealed that &lt;strong&gt;the only way an EC2 ECS task can make an external call is via NAT&lt;/strong&gt;. Worth noting that Fargate and EC2 don’t have this limitation. I am not sure we would have used ECS EC2 if we knew about it - NATs are expensive.&lt;/p&gt;

&lt;p&gt;When we added the NAT we also added a routing rule (as documented). Outgoing traffic from zone 2b (the zone with the task) will be routed via the NAT. The actual architecture that I worked on was actually:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foxgxiwktv8tze5m1ffc5.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foxgxiwktv8tze5m1ffc5.jpg" alt="Architecture diagram with two LB in two zones connecting to one ECS task, ECS task has outgoing arrow to a NAT in the other zone"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With these routing rules, traffic that users initiated went through the internet gateway (this is the default way services with public IP address talk to the internet in AWS) to the LB to the task and then back. Outbound traffic went to the NAT. &lt;/p&gt;

&lt;p&gt;Or so we thought. &lt;/p&gt;

&lt;p&gt;The routing rule that we added to zone 2b for the NAT didn’t really say “outbound traffic goes via NAT”. It rather said: “If destination is anywhere outside the VPC, go via NAT”. I assumed it applied to outbound only, but our load balancer is in the subnet that has these rules. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What if any time the LB tries to send a response to a user, it actually gets routed via the NAT and AWS networking doesn’t deal with this very well?&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;I have no proof for this theory. I spent a bunch of time playing with AWS's network reachability analyzer. It is nifty, but I couldn't create the routes that would conclusively prove this theory. &lt;/p&gt;

&lt;p&gt;But I was out of any more ideas, so I decided that if the theory is right, I need to get the LB that is in zone 2b to a zone that doesn't have the NAT routing rule.&lt;/p&gt;

&lt;p&gt;Why not move the LB to zone 2c? Turns out that ECS requires an LB to exist in the zone where the task exists. &lt;/p&gt;

&lt;p&gt;We ended up with a messy solution and a better solution for the long term. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Messy solution:&lt;/strong&gt; &lt;br&gt;
Modify the routing rules. Instead of routing all traffic that goes to the internet via the NAT, we’ll route only traffic going to Google via the NAT. Luckily Google published its IP ranged, so I could add routing rules specific to it: &lt;a href="https://www.gstatic.com/ipranges/goog.txt" rel="noopener noreferrer"&gt;https://www.gstatic.com/ipranges/goog.txt&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This isn't a great solution because we'll need to add more routes for every external service we'll need to access.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Better solution:&lt;/strong&gt;&lt;br&gt;
Use the fact that routing rules apply to &lt;strong&gt;subnets&lt;/strong&gt; but ECS tasks need an LB in the same &lt;strong&gt;zone&lt;/strong&gt;. So we can create two subnets in each zone. One with virtual network equipment and the other with tasks. Something like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fma84sya0c9xkhy79hv6r.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fma84sya0c9xkhy79hv6r.jpg" alt="Architecture diagram with two subnets in each one of two zones. Public subnet has LB and NAT. Private subnet has ECS task"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is very similar to pretty much every diagram AWS publishes about how to set up public internet access for services. They all look like this:&lt;/p&gt;

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

&lt;p&gt;I just wish they explained why they set it up that way and what happens if you don’t. But here is the full story, at least my readers won't need to learn about it the hard way.&lt;/p&gt;




&lt;p&gt;Enjoyed reading this? &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Read more about &lt;a href="https://docs.thenile.dev/blog" rel="noopener noreferrer"&gt;Infra SaaS and Control Planes on my company blog&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Or join the &lt;a href="https://launchpass.com/all-about-saas" rel="noopener noreferrer"&gt;SaaS Developer Community Slack&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>devops</category>
      <category>aws</category>
    </item>
    <item>
      <title>Apps for Small Things that Matter</title>
      <dc:creator>Gwen (Chen) Shapira</dc:creator>
      <pubDate>Thu, 19 May 2022 16:33:20 +0000</pubDate>
      <link>https://dev.to/gwenshap/apps-for-small-things-that-matter-pgp</link>
      <guid>https://dev.to/gwenshap/apps-for-small-things-that-matter-pgp</guid>
      <description>&lt;p&gt;Some apps that help me stay sane and healthy on weeks when there is a lot going on and it is easy to get stuck on VSCode, Slack or Twitter for 14h straight:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Routinery&lt;/strong&gt;: Lets you configure a routine with steps, and it has a timer per step, so you don't get stuck on one thing or get distracted. This helps me remember to drink tea, meditate and sort out my schedule for the day before turning to any inbox. Routinery also reminds me to get off work at 6pm, move my body, eat and spend time with humans. I ignore it more often than I want to admit (very tempting to just keep going on an interesting problem or the pile of small tasks), but it feels good when I don't.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;DayStamp:&lt;/strong&gt; it is the least judgmental "habits app" that I know. I added about 20 things I'd love to do on a regular basis. Some I do daily, some weekly, some rarely. I use it to check if things got out of balance (didn't work out in a week, didn't code or publish anything in a week, didn't read anything meaningful in a month...)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Wakeout:&lt;/strong&gt; 1-5 min workouts that can be done at the desk or couch. I have no excuse not to do them (1 minute is nothing!), and it helps me move a bit few times a day. It did wonder to my back/shoulder which used to get stiff after long days. Some of the workouts in the app are very silly, but I love the "office yoga" one. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Sweepy:&lt;/strong&gt; Prioritizes cleaning tasks and tracks how bad my home got. I can see when the sink is full, but I don't actually see if the floor or bathroom mirror got dirty (I think this is a common problem). As a result, sometimes my house gets to the point where my friends visit and become seriously concerned ("how can you live like this? it is like a pigsty!"). This app helps me "see" that I didn't clean the floor in a while and it is certainly dirty.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Whatsapp, Facetime, SMS, email:&lt;/strong&gt; My family and close friends are the most important thing in my life, and definitely the most important contributor to my happiness. Grateful for the apps and protocols that help us stay in touch despite time and distance challenges.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Apps that I don't use:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Pomodoro apps: I like the 25 minute or 50 minute work spans followed by a quick break. But I didn't find an app that I like, so I just use my alarm clock. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Meditation apps: You can't convince me to pay money for an app that tells me to close my eyes and breath. I use a timer for this one too. &lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>productivity</category>
    </item>
    <item>
      <title>Generating docs from OpenAPI Spec</title>
      <dc:creator>Gwen (Chen) Shapira</dc:creator>
      <pubDate>Wed, 27 Apr 2022 05:18:36 +0000</pubDate>
      <link>https://dev.to/gwenshap/generating-docs-from-openapi-spec-4j3i</link>
      <guid>https://dev.to/gwenshap/generating-docs-from-openapi-spec-4j3i</guid>
      <description>&lt;p&gt;It started with a very simple setup. Two github repositories: One for our backend, which included OpenAPI specs of our backend APIs. The second for our documentation website, which we based on Facebook's &lt;a href="https://docusaurus.io/"&gt;Docusaurus&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;I chose Docusaurus for the docs website because about 500 of my closest friends recommended it. And it was a great choice - I could write docs using Markdown and not worry about anything else, Docusaurus magically turned them into a great looking website. &lt;/p&gt;

&lt;p&gt;Until the point where we wanted to introduce generated API documentation to our docs. We wanted the generated docs to be integrated with the rest of the docs site. It should really feel like a single experience. &lt;/p&gt;

&lt;p&gt;The first iteration involved adding a small build script to the docs repo that cloned the backend repo and used &lt;a href="https://github.com/syroegkin/swagger-markdown"&gt;swagger-markdown&lt;/a&gt; on each file to generate markdown. Docusaurus found the markdown files and did the rest. &lt;/p&gt;

&lt;p&gt;I wasn't super happy with the generated docs, but it worked for a while and we left it alone. Until Monday, when we merged a change that caused us to trigger a &lt;a href="https://github.com/syroegkin/swagger-markdown/issues/169"&gt;bug in swagger-markdown&lt;/a&gt;. The issue was reported more than 6 month back and has no responses. Which raised some concerns about whether it is a good project to depend on - very little activity, very few forks and stars... it didn't look good. 👎&lt;/p&gt;

&lt;p&gt;So I did some shopping around. Here's what I tried, what worked and what didn't:&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://github.com/Mermade/widdershins"&gt;Widdershins&lt;/a&gt; 👎
&lt;/h3&gt;

&lt;p&gt;This project looks fantastic. Tons of cool features, customizations and lots of github stars. But... I ran into &lt;a href="https://github.com/Mermade/widdershins/issues/326"&gt;this bug&lt;/a&gt;. The bug was fixed almost 2 years ago, but the project didn't have a single release since. I could have probably figured out a script that didn't require a release, but... this project is clearly just a single person who doesn't have time to do a release in two years. I didn't feel good depending on that either.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://github.com/OpenAPITools/openapi-generator"&gt;OpenAPI Generator&lt;/a&gt; 👎
&lt;/h3&gt;

&lt;p&gt;This looked very official. The documentation wasn't great. When I tried it, it left a lot of "junk" in my working director. But the real problem was that it generated an entire directory structure of markdown for each input spec - and it seemed really painful to tie this into the main Docusaurus site. I could have probably made it work - but the  minimal docs and messy experience got me to look around a bit more.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://redocusaurus.vercel.app/docs"&gt;Redocusaurus&lt;/a&gt; ✅
&lt;/h3&gt;

&lt;p&gt;This project was simple to install, simple to use, and was built exactly for my use-case. API docs in Docusaurus with a unified experience for users. It didn't have many github stars, but it wrapped the hugely popular redoc. And most important - the author is active, responsive and kind. Just check out the issues - he comes across as someone you want to work with. To put the icing on the cake - &lt;a href="https://developers.forem.com/api"&gt;Forem&lt;/a&gt;, the engine behind this very website, uses this plugin. How cool is that?&lt;/p&gt;

&lt;p&gt;I was all ready to use Redocusaurus, but there was one problem:&lt;/p&gt;

&lt;p&gt;Our specs were split between several YAML spec files. It looked like a good idea when we did that - large files are not fun to work with. The problem is that very little in the OpenAPI ecosystem was built for multiple files. I strongly recommend that you will save yourself the pain and go the mono-file route. If &lt;a href="https://github.com/stripe/openapi"&gt;Stripe can have a 4.5MB spec file&lt;/a&gt;, so can we. &lt;/p&gt;

&lt;p&gt;In order to use Redocusaurus, I needed a single spec file. Both &lt;a href="https://github.com/APIDevTools/swagger-cli"&gt;Swagger cli&lt;/a&gt; and &lt;a href="https://github.com/Redocly/openapi-cli"&gt;OpenApi cli&lt;/a&gt; offered an option to merge separate specs into one. The problem was that one of them required a "root spec file" to drive the merging and the other required extra information to resolve conflicts. My specs had neither. &lt;/p&gt;

&lt;p&gt;I ended up with an old fashioned solution - manually merging the spec files for now. Our engineering team has thoughts on how to improve our use of OpenAPI specs, and we'll have a better solution in a week or two as a result of that. &lt;/p&gt;

&lt;p&gt;I hope this blog helps someone who has similar requirements or just tries to pick between 3 JS projects who all do similar things. Responsiveness of maintainers is really important.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>api</category>
      <category>docs</category>
    </item>
    <item>
      <title>Choosing Technologies, APIs and Languages</title>
      <dc:creator>Gwen (Chen) Shapira</dc:creator>
      <pubDate>Tue, 29 Mar 2022 21:47:56 +0000</pubDate>
      <link>https://dev.to/gwenshap/choosing-technologies-apis-and-languages-2319</link>
      <guid>https://dev.to/gwenshap/choosing-technologies-apis-and-languages-2319</guid>
      <description>&lt;p&gt;There is a vast difference between choosing a technology or a language for one person or a small team, choosing for a large engineering organization, and choosing for a platform with potentially tens of thousands of users. &lt;/p&gt;

&lt;p&gt;Choosing a technology for yourself or a small team is usually about personal taste, whether or not you are interested in learning something new, available tools, integration into an existing project, and perhaps performance/scale considerations. &lt;/p&gt;

&lt;p&gt;When you choose a technology for a larger engineering organization, perhaps an entire company, it's a different story. In that case, you need to think about hiring, training, use-cases, testing, all the other parts of the CI/CD pipelines, and try to imagine three years into the future - is this technology growing or dying?&lt;/p&gt;

&lt;p&gt;Choosing a technology for a platform should be about the people who will use it. What will they find natural? What will make their life easier? What will create the best experience? It is very tempting to build something for yourself and hope that others will like it, but I think we can do better. We can connect to our potential users and try to emphasize, listen to them and see things from their perspective so you can build a fantastic experience for them. &lt;/p&gt;

&lt;p&gt;This isn't easy, especially if you hope that front-end engineers will use your product and you only know two. &lt;/p&gt;

&lt;p&gt;Can you help me out by sharing your thoughts in a quick survey? &lt;a href="https://0sri4j4i8ze.typeform.com/to/IWI56Zkk"&gt;https://0sri4j4i8ze.typeform.com/to/IWI56Zkk&lt;/a&gt; I ask about your favorite languages and APIs as I make decisions about the platform I'm building. I really appreciate all the help! Feel free to comment below with more feedback, I'll appreciate this even more.&lt;/p&gt;

&lt;p&gt;P.S&lt;br&gt;
Chris Ricominni has a great blog post on &lt;a href="https://cnr.sh/essays/preventing-technology-turf-wars"&gt;how to introduce new technologies to an organization&lt;/a&gt; - for the more practical aspects. &lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>react</category>
    </item>
    <item>
      <title>What is a High Quality Product?</title>
      <dc:creator>Gwen (Chen) Shapira</dc:creator>
      <pubDate>Mon, 31 Jan 2022 06:13:25 +0000</pubDate>
      <link>https://dev.to/gwenshap/what-is-a-high-quality-product-3odk</link>
      <guid>https://dev.to/gwenshap/what-is-a-high-quality-product-3odk</guid>
      <description>&lt;p&gt;When you are building a product from scratch, as my co-founders and I are doing right now, it is easy to become very passionate about doing very high quality work. We constantly talk about how we want to make everything "World Class".&lt;/p&gt;

&lt;p&gt;Quality is hard to define though. One of my favorite books growing up was &lt;a href="https://www.amazon.com/dp/B0063HC7EQ/ref=dp-kindle-redirect?_encoding=UTF8&amp;amp;btkr=1"&gt;"Zen and the Art of Motorcycle Maintenance"&lt;/a&gt;, and a good chunk of the book is about the author trying to define quality and what happens to him as a result. Quality is something that you know when you see. High quality products give this impression as a strong immediate experience. You read a story and the first sentence grabs you and you can't let go. But there is also an element of context and experience - it gives you a wider range of quality experiences. I enjoy some wines more than others, but I can't really understand great wines. On the other hand, after 20 years of code reviews, I can tell a lot about an engineer by looking at their code and can distinguish good code from great in different domains, paradigms and languages. &lt;/p&gt;

&lt;p&gt;When it comes to software products, I believe that the final judge of quality is the customer. We talked to a lot of potential customers about what makes an experience high quality for them.&lt;/p&gt;

&lt;p&gt;A lot of companies and cultures confuse "lack of bugs" with "quality". There is some overlap, but the Venn Diagram is not a circle. We all know software that was buggy and immature but compelling enough to still provide high quality experience. We also know software that is nearly bug free but the experience doesn't feel high quality. &lt;/p&gt;

&lt;p&gt;If lack of bugs isn’t it, what makes for high quality product?&lt;/p&gt;

&lt;p&gt;Here is my definition: &lt;em&gt;High quality product offers a magical experience to a user in a specific dimension that they really care about.&lt;/em&gt; A lot of other things can be forgiven if you can be truly magical in the specific things that matter to them.&lt;/p&gt;

&lt;p&gt;Few examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Early Slackware Linux had tons of bugs, but it was the first time I could run a Unix on my desktop at home and not the computer lab at the university. Changed my life.&lt;/li&gt;
&lt;li&gt;My first car navigation system kept crashing, but it was still way better than stopping to look at maps.&lt;/li&gt;
&lt;li&gt;Early Kafka was easy to get started with and it had amazing uptime. There were major bugs and people reported bugs and kept using it. Eventually the bugs were resolved.&lt;/li&gt;
&lt;li&gt;Early Twitter and the fail whale. &lt;/li&gt;
&lt;li&gt;Datadog was super simple to get started with and sending metrics "just worked", we had some issues with reporting that they fixed later, but we remained a customer forever.&lt;/li&gt;
&lt;li&gt;Expensify allowed me to take photos of receipts and not carry them around.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The take-way here is that you need to &lt;em&gt;figure out what your users really care about, especially in their early adoption steps, and make it feel magical&lt;/em&gt;. &lt;/p&gt;

&lt;p&gt;Very early in my Confluent career, we hired an amazing training developer (she went on to be much more). On her second day, she said "I want to structure my training around a practical example, what is a fun thing to do with Kafka?" and I was the PM of Connect, so I said - "Why not get some data from MySQL to Kafka, do simple aggregation with Kafka Streams, and write the result to S3?". This was already a fairly popular use-case in my mind. And 2 days later she said, "something is wrong, it doesn't work". She was right, none of this "just worked", you had to figure out specific configurations, specific formats, specific steps. It took us weeks to get it to work. And we saw this as a basic use-case! This was completely “green path” - no chaos, no high load, nothing that should have been challenging. &lt;/p&gt;

&lt;p&gt;Note that a QA teams rarely finds these kinds of issues, and the issues she found were not in any one part of the product. It was either usability issues or more frequently integration issues - you only see them when you try to use your product like a real customer and implement an entire workflow. We eventually built automated testing framework specifically around real customer workflows. &lt;/p&gt;

&lt;p&gt;And to close on a more quantifiable note, one last tip for quality:&lt;br&gt;
How often bad things happen, or if they start happening more frequently, is actually really important thing to know when thinking about user experience in SaaS. SLOs is a good tool around this, but many years ago I learned about a more flexible tool that is worth knowing about. It is called a &lt;a href="https://en.wikipedia.org/wiki/Control_chart"&gt;control chart&lt;/a&gt;. You basically take a metric, say response time latency, and you plot it over time. You then define a range of "normal values", it can be an overall average, average per entity like machine or user, or an adaptive average that can handle things like weekend use and rush hour. Now you'll have a set of points outside the "normal range" and you can define rules on them:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Any point 3 standard deviations above the baseline. This will indicate extreme sudden increase.&lt;/li&gt;
&lt;li&gt;5 consecutive measurements more than one standard deviation over the baseline. This indicates a sustained increase.&lt;/li&gt;
&lt;li&gt;10 consecutive measurement each higher than previous one. This indicates steady upward trend.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is a super flexible way to detect and communicate a wide range of quality issues in a production system. So you can discuss not just a specific incident but worrying trends.&lt;/p&gt;

&lt;p&gt;I also posted this content in a video, if you prefer:&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/3WoV86d7-c4"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

</description>
      <category>codequality</category>
      <category>product</category>
      <category>ux</category>
      <category>devrel</category>
    </item>
    <item>
      <title>Lessons learned when building self-serve provisioning</title>
      <dc:creator>Gwen (Chen) Shapira</dc:creator>
      <pubDate>Tue, 25 Jan 2022 23:24:19 +0000</pubDate>
      <link>https://dev.to/gwenshap/lessons-learned-when-building-self-serve-provisioning-4c11</link>
      <guid>https://dev.to/gwenshap/lessons-learned-when-building-self-serve-provisioning-4c11</guid>
      <description>&lt;p&gt;I've talked to ~20 companies that are at various stages of building data infrastructure (and sometimes ML, analytics and other infrastructure). Many of them have a step when signing up customers that require provisioning some cloud resources for the customers. &lt;/p&gt;

&lt;p&gt;I was at Confluent at the time when we moved from "talk to sales and wait a week" manual provisioning to self-serve provisioning. The way different companies go about this are different some of the lessons are generally applicable. Ram Subramanian, who was the VP Eng at Confluent who ran the entire cloud service recorded a vide with me where we discussed the lessons we've learned:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Manual process, especially one when there are multiple humans between the customer and "get something done" introduces risk of getting things wrong and pissing off the customer.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Not only does it piss off customers, a lot of mistakes means a lot of back-and-forth and is a huge waste of time.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;This gets much worse as growth accelerates. And customer expectations go up over time too.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Since building actual product automation (as opposed to partially-automated collection of scripts) takes some time (especially if you need it to be well tested since your customers are already a bit annoyed), you actually need to start before this is a real painful problem.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;It doesn't take much to go from a point of "manual is fine" to "our engineers spend so much time on manual work that they don't have time to work on automation" and from there to "our best engineers quit".&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Another thing we learned is that there is a huge gap between "automated for internal use" and "self serve for customers". For real customer facing you need much better feedback loop around "what is happening? are there any issues? is it making progress? what does this error mean and what do I do now?" and you also need much better guard rails - customer mistakes can bring down your system or cost you a lot if you are not careful about how you build it.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/w9MaX2WuUGA"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;P.S&lt;br&gt;
We started a series of "SaaS Stories" - I am trying to interview people who built great SaaS products to talk about how they built some of the foundational SaaS experience and share what they learned.  Especially about things that may look basic but turned out to be much harder than anyone expected. Warning each other about potential pitfalls and potential solutions seems like a great cause.  So, if you want to join the series and talk about how you built your on-boarding, authorization, multiple product tiers, billing, notifications, user and org management, etc... I'd love to chat. No vendors talking about their solutions - only people building SaaS products sharing their lessons. Ping me via &lt;a class="mentioned-user" href="https://dev.to/gwenshap"&gt;@gwenshap&lt;/a&gt; on twitter. TY!&lt;/p&gt;

</description>
      <category>devops</category>
      <category>aws</category>
    </item>
    <item>
      <title>What I learned about using and building public APIs</title>
      <dc:creator>Gwen (Chen) Shapira</dc:creator>
      <pubDate>Sat, 15 Jan 2022 03:28:11 +0000</pubDate>
      <link>https://dev.to/gwenshap/what-i-learned-about-using-and-building-public-apis-2d00</link>
      <guid>https://dev.to/gwenshap/what-i-learned-about-using-and-building-public-apis-2d00</guid>
      <description>&lt;p&gt;I've recently learned about the API Economy concept (&lt;a href="https://www.notboring.co/p/apis-all-the-way-down"&gt;A good primer&lt;/a&gt; and &lt;a href="https://www.swyx.io/api-economy/"&gt;a contrarian view&lt;/a&gt;). After a bit of research, I was pretty amazed at how so much of the stuff you need to build your product already exists out there. I think everyone knows about Stripe, Twilio, Auth0.. but I was surprised to learn about Notarize, which has API for signing and notarizing documents, or FaunaDB which is a database with REST API, or background checks with API, etc, etc.&lt;/p&gt;

&lt;p&gt;I learned to never implement anything without checking if someone else already provides that as a service via an API that I could integrate with. I found out that Postman (which also has a nice desktop app for testing APIs) hosts API world where tons of APIs are hosted and it lets you experiment with them online: &lt;a href="https://www.postman.com/explore"&gt;https://www.postman.com/explore&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I also learned about JAMStack, which is a community of FE engineers who are building entire websites without ever building a backend - simply by using other services and filling in the gaps with serverless functions (AWS Lambda and such).&lt;/p&gt;

&lt;p&gt;It makes sense for pretty much every service to add public APIs and allow others to integrate. It opens up new use-cases and revenue streams, and for the most part we already have these APIs. OpenAPI makes it pretty easy to define the APIs, and then generate the web interfaces in your language, generate documentation, generate mocks, generate tests, etc... All those generated stuff is super important: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Developers will need the documentation to use your API and do the integrations. &lt;/li&gt;
&lt;li&gt;Mocks will allow them to test the integration without loading your production application. Maybe even try a "mocked MVP" so you can get feedback before building your app.&lt;/li&gt;
&lt;li&gt;Generated tests will let you continuously validate that you didn't break the APIs (since other developers will rely on their for their applications, it is critical not to break their applications - they won't come back and tell their friends). &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://swagger.io/"&gt;https://swagger.io/&lt;/a&gt; has great tools for OpenAPI.&lt;/p&gt;

&lt;p&gt;I cover all this with a bunch of examples in a video: &lt;br&gt;
&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/cTnTW5Cq0b0"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;I'm also working on a SaaS-in-a-Box backend service (hopefully with both great APIs and great integrations) to make life simpler for SaaS developers. If you are willing to give me feedback on the MVP, mind sharing your email with me in this form? I promise it won't be used for marketing, I'll just personally connect with you to discuss &lt;a href="https://forms.gle/8tW73MwEWdWu4rB27"&gt;https://forms.gle/8tW73MwEWdWu4rB27&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>beginners</category>
    </item>
  </channel>
</rss>
