<?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: Sayan Chakraborty</title>
    <description>The latest articles on DEV Community by Sayan Chakraborty (@shockroborty).</description>
    <link>https://dev.to/shockroborty</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%2F398727%2F51171873-69d2-45da-844e-309e4b8ad228.JPG</url>
      <title>DEV Community: Sayan Chakraborty</title>
      <link>https://dev.to/shockroborty</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/shockroborty"/>
    <language>en</language>
    <item>
      <title>Inspect yourself before you wreck yourself</title>
      <dc:creator>Sayan Chakraborty</dc:creator>
      <pubDate>Sun, 16 Aug 2020 09:22:29 +0000</pubDate>
      <link>https://dev.to/shockroborty/inspect-yourself-before-you-wreck-yourself-1o8c</link>
      <guid>https://dev.to/shockroborty/inspect-yourself-before-you-wreck-yourself-1o8c</guid>
      <description>&lt;p&gt;&lt;a href="https://sayan.xyz/posts/elixir-erlang-debugging-otp"&gt;&lt;strong&gt;&lt;em&gt;Link to the original post&lt;/em&gt;&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Our live-streaming game engine's comments system crashed mid-way, with no significant code changes in over a year for the modules that implements the feature.&lt;/p&gt;

&lt;p&gt;To shed some context, the engine is built with Elixir/Erlang and MQTT brokers. It leverages all the sweet concurrency and fault-tolerance abstractions Erlang and OTP provides out of the box, so ideally it should never go down; but this was an interesting day in the field.&lt;/p&gt;

&lt;h3&gt;
  
  
  Uh-Oh!
&lt;/h3&gt;

&lt;p&gt;So what happened? Our clients talk to engine and engine talks back to them with data via MQTT broker - a highly optimised pub-sub broker. Each message type has its own topic. We could see live player count working and increasing, so clearly other supervision trees and features were fine but for some reason comments system was down.&lt;/p&gt;

&lt;p&gt;Was our MQTT broker bonking for clients because of load?&lt;/p&gt;

&lt;p&gt;We went to our MQTT broker dashboard and checked the load, it was under-utilized. The subscription topics showed up as well in the listeners tab. I manually subscribed to the player count topic from console and received timely message of user count on it. So MQTT is sending messages fine, did the MQTT disconnect happen for all the comments workers?&lt;/p&gt;

&lt;p&gt;We checked Observer (a very helpful OTP tool to visualize VM and process data) and found no Comments workers in the set of processes live.&lt;/p&gt;

&lt;p&gt;What happened?&lt;/p&gt;

&lt;p&gt;Visit Sentry and we find multiple occurences of this error below in the worker code that moderates and publishes comments.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--d5RovHBh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/2vkcw07k420rx5p9y620.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--d5RovHBh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/2vkcw07k420rx5p9y620.png" alt="Protocol Sentry Error Report"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Huh? We checked the DynamicSupervisor spec, it takes the default configuration. &lt;/p&gt;

&lt;p&gt;From HexDocs:&lt;br&gt;
&lt;code&gt;:max_restarts - the maximum number of restarts allowed in a time&lt;/code&gt;&lt;br&gt;
&lt;code&gt;frame. Defaults to 3.&lt;/code&gt; &lt;/p&gt;

&lt;p&gt;So that checks out - the Supervision tree will and should go down after trying to come back up 3 times.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bug in the system? GIT BLAME!
&lt;/h3&gt;

&lt;p&gt;We follow the line number from Sentry and see this code - &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s---nWFpneN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/81hjojt8z3glme8julg6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s---nWFpneN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/81hjojt8z3glme8julg6.png" alt="Protocol code bug"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Do you see the bug in this (seemingly-harmless) function? Did you find it?&lt;/p&gt;

&lt;p&gt;It's the statement inside &lt;code&gt;Logger.info&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;"#{}"&lt;/code&gt; expects the Data structures mentioned in the Sentry error inside the interpolation block. All it needed was &lt;code&gt;#{inspect(request)}&lt;/code&gt;&lt;br&gt;
because the Sentry error showed us that it got &lt;code&gt;{#Reference&amp;lt;0.1573182675.3632529412.140924&amp;gt;, {:ok,&lt;/code&gt;&lt;br&gt;
&lt;code&gt;"a77eb2e7744d4c29b12ec3a5fc3c731f"}}&lt;/code&gt; - a &lt;em&gt;Tuple&lt;/em&gt; instead. &lt;code&gt;inspect&lt;/code&gt; takes care of casting any ambiguous data types like these and is really helpful in logging.&lt;/p&gt;

&lt;h4&gt;
  
  
  Wait, but this is a 14 months old code and all the test cases never come here, infact comments system worked fine for all the past events.
&lt;/h4&gt;

&lt;p&gt;As the comment inside the function says, it's a catch-all for handling unhandled &lt;code&gt;handle_info&lt;/code&gt; messages.&lt;/p&gt;

&lt;p&gt;The intent was correct (also harmless, because the workers would've worked fine without this function), the implementation was not. Either way, it should have never come there randomly for a &lt;code&gt;tuple&lt;/code&gt; we don't know the origin of.&lt;/p&gt;

&lt;h3&gt;
  
  
  So what kind of messages does &lt;code&gt;handle_info&lt;/code&gt; gets?
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;Besides the synchronous and asynchronous communication&lt;/code&gt;&lt;br&gt;
&lt;code&gt;provided by call/3 and cast/2, "regular" messages sent by&lt;/code&gt;&lt;br&gt;
&lt;code&gt;functions such as Kernel.send/2, Process.send_after/4 and similar,&lt;/code&gt;&lt;br&gt;
&lt;code&gt;can be handled inside the handle_info/2 callback.&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Our message publishing was indeed a recursive &lt;code&gt;Process.send_after&lt;/code&gt;, but why would it randomly send an unhandled &lt;code&gt;{#Reference&amp;lt;0.1573182675.3632529412.140924&amp;gt;, {:ok,&lt;/code&gt;&lt;br&gt;
&lt;code&gt;"a77eb2e7744d4c29b12ec3a5fc3c731f"}}&lt;/code&gt; out of no where. That didn't add up. Rest of the code relied on &lt;code&gt;GenServer.cast&lt;/code&gt;, so no where this random tuple could've come from.&lt;/p&gt;

&lt;p&gt;We checked - its not an UUID or an API-KEY of our eco-system.&lt;/p&gt;

&lt;p&gt;Strange?&lt;/p&gt;

&lt;p&gt;Digging little further, I found this - &lt;a href="https://github.com/ninenines/gun/issues/193" rel="noopener noreferrer nofollow"&gt;closed Github issue in Gun library&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--UtnMfRDC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/dyoz7mhuczfoiriz6r0b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--UtnMfRDC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/dyoz7mhuczfoiriz6r0b.png" alt="Gun lib ref"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ah-ha! Someone with the same random tuple pattern in their &lt;code&gt;handle_info&lt;/code&gt;s. And we were also on OTP 20.&lt;/p&gt;

&lt;p&gt;Gun Library is from NineNines, the people who built Cowboy server. Pretty solid source.&lt;/p&gt;

&lt;p&gt;Apparently, it starts happening under high load -&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--USN-zmul--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/g7maz0z1bqthoqldmai7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--USN-zmul--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/g7maz0z1bqthoqldmai7.png" alt="Gun live issue comment"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;They filed a bug in Erlang for this stray message that gets bubbled up on TCP close - &lt;a href="https://github.com/ninenines/gun/issues/193" rel="noopener noreferrer nofollow"&gt;&lt;/a&gt;&lt;a href="https://bugs.erlang.org/browse/ERL-1049"&gt;https://bugs.erlang.org/browse/ERL-1049&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--55zfpxRU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/uu1thsc6j5c497a81av3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--55zfpxRU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/uu1thsc6j5c497a81av3.png" alt="Erlang issue"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Our MQTT connections were indeed via TCP protocol from engine to broker.&lt;/p&gt;

&lt;p&gt;And it's fixed in OTP 22.1.1 patch. Yay!&lt;/p&gt;

&lt;h3&gt;
  
  
  Pro-tip
&lt;/h3&gt;

&lt;p&gt;Always encapsulate your variables with &lt;code&gt;inspect&lt;/code&gt; in Elixir while interpolating strings in Logger or sending them as arguments to Sentry.&lt;/p&gt;

</description>
      <category>elixir</category>
      <category>erlang</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Elixir and ETS Alchemy</title>
      <dc:creator>Sayan Chakraborty</dc:creator>
      <pubDate>Sat, 30 May 2020 21:24:57 +0000</pubDate>
      <link>https://dev.to/shockroborty/elixir-and-ets-alchemy-55ff</link>
      <guid>https://dev.to/shockroborty/elixir-and-ets-alchemy-55ff</guid>
      <description>&lt;p&gt;&lt;a href="https://sayan.xyz/posts/elixir-erlang-and-ets-alchemy" rel="noopener noreferrer"&gt;&lt;strong&gt;&lt;em&gt;Link to the original post&lt;/em&gt;&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;While Phoenix is becoming a popular MVC framework to build modern web applications with, diving deeper into the BEAM VM and OTP can seem quite daunting at first. If you're familiar with Elixir/Erlang and OTP, you'd have already heard about ETS. While there are other powerful abstractions like GenServers, Agents, GenStage, Observer, etc that the BEAM VM and OTP provide out of the box, let's dive deeper into ETS in this post.&lt;/p&gt;

&lt;p&gt;Couple of topics we'll tackle in this post are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What is ETS?&lt;/li&gt;
&lt;li&gt;When should you use ETS in your application&lt;/li&gt;
&lt;li&gt;ETS and table management&lt;/li&gt;
&lt;li&gt;ETS in production&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Before we dive into these topics, a brief disclaimer - &lt;/p&gt;

&lt;p&gt;&lt;em&gt;I will use the terms Elixir and Erlang interchangeably in this post since Elixir programming language has Erlang interoperability similar to what Kotlin and Clojure has for Java. Also, you can skip the first section if you're already familiar with the basics of ETS.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What is ETS?
&lt;/h2&gt;

&lt;p&gt;ETS or Erlang Term Storage is an in-memory key-value data store that OTP provides to store tuples of Erlang terms. ETS data lookups are fast and of constant time, except in the case of &lt;code&gt;ordered_set&lt;/code&gt; ETS table. There are 4 types of ETS tables -&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;:set&lt;/code&gt; is the default type of ETS table, with a single object/value against unique keys.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;:ordered_set&lt;/code&gt;, as the name suggests, is the ordered cousin of &lt;code&gt;:set&lt;/code&gt; type. It guarantees retrieval order and the access time is proportional to the logarithm of the number of stored objects.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;:bag&lt;/code&gt; type enables object insertion with duplicate keys, but the entire row shouldn't be identical.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;and lastly &lt;code&gt;:duplicate_bag&lt;/code&gt;, which allows duplicate rows and has the least definition on data insertion.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;code&gt;insert/2&lt;/code&gt; function is consistent across different table types and guaranteed to be atomic in nature, but they are destructive updates in nature. You can unwittingly shoot yourself in the foot with overwriting sensitive data, so you'd want to be familiar with &lt;a href="https://erlang.org/doc/man/ets.html#insert_new-2" rel="noopener noreferrer"&gt;insert_new&lt;/a&gt; function which returns a boolean flag in case of insertion failure due to duplicity of key.&lt;/p&gt;

&lt;p&gt;You can pass more options on top of type during ETS initialization to leverage optimizations as per need -&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;:named_table&lt;/code&gt; - By default, ETS tables are unnamed and can be accessed only by their reference that is returned during initialization. You can instead use this option to set a name to the tables for access.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;:public&lt;/code&gt;/&lt;code&gt;:protected&lt;/code&gt;/&lt;code&gt;:private&lt;/code&gt; - You can set the table's access rights. These rights dictate what processes are able to access the table. We'll discuss their implications later in this post.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;:read_concurrency&lt;/code&gt;/&lt;code&gt;:write_concurrency&lt;/code&gt; - These concurrent performance tuning optimization flags are set to &lt;code&gt;false&lt;/code&gt; by default for both of these values. These options make read and write throughput extensively cheaper if bursts of read and write operations are common in the application, however, interleaved read and write operations will accrue penalty in performance.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;:compressed&lt;/code&gt; - Disabled by default, but enabling makes the table data be stored in a more compact format to consume less memory. However, it will make table operations slower.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;:decentralized_counters&lt;/code&gt; - Defaults to true for &lt;code&gt;:ordered_set&lt;/code&gt; tables with &lt;code&gt;:write_concurrency&lt;/code&gt; enabled, and defaults to false for all other table types. This option has no effect if the write_concurrency option is set to false. When this option is set to true, the table is optimized for frequent concurrent calls to operations that modify the tables size and/or its memory consumption.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Example ETS initialization with options:&lt;/em&gt;&lt;/p&gt;


&lt;blockquote&gt;
&lt;br&gt;
    {:ok, table_ref} = :ets.new(:example_table, [:set, :named_table, :protected, read_concurrency: true, write_concurrency: true])&lt;br&gt;
  &lt;/blockquote&gt;

&lt;h2&gt;
  
  
  When should you use ETS in your application
&lt;/h2&gt;

&lt;p&gt;Official Elixir documentation uses ETS as a cache, but that is just one of the many use cases. You could very well use the process (GenServer/Agent) state as cache, so how do you recognize the need for ETS in your processes. Recognizing the need for ETS tables is essential especially when the official documentation itself mentions the warning below while showing ETS's use cases. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Warning! Don’t use ETS as a cache prematurely! Log and analyze your application performance and identify which parts are bottlenecks, so you know whether you should cache, and what you should cache. This chapter is merely an example of how ETS can be used, once you’ve determined the need.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;What is so special about ETS tables that process states don't fulfill?. After all, they are, as transient as process state. They die when the owner process that has spawned them die as well (unless an &lt;code&gt;heir&lt;/code&gt; is declared, we'll get to that later).&lt;/p&gt;

&lt;p&gt;ETS tables are stored in off-heap memory, so your Garbage Collection on a process doesn't do overtime for the frequent data operations on ETS tables as they are not garbage collected till the time process is alive. Since ETS is a data store, it provides excellent abstractions to query on data in an optimized and efficient way, some of these abstractions are &lt;code&gt;lookup/2&lt;/code&gt;, &lt;code&gt;lookup_element/3&lt;/code&gt;, &lt;code&gt;match/2&lt;/code&gt;, &lt;code&gt;match_object/2&lt;/code&gt; and &lt;code&gt;select/2&lt;/code&gt; functions &lt;em&gt;(&lt;code&gt;select&lt;/code&gt; function's queries are not quite developer-friendly, so you could use &lt;code&gt;fun2ms/2&lt;/code&gt; function to create queries that &lt;code&gt;select&lt;/code&gt; function can ingest)&lt;/em&gt;. You can see its usage here - &lt;a href="https://elixirschool.com/en/lessons/specifics/ets/#advanced-lookup" rel="noopener noreferrer"&gt;Elixir School&lt;/a&gt;. You should use ETS tables if you want to operate on large set of data and query them efficiently.&lt;/p&gt;

&lt;p&gt;While &lt;code&gt;:private&lt;/code&gt; access ETS tables do not allow read and write operations outside the owner process, &lt;code&gt;:protected&lt;/code&gt; access tables allow read access to all the processes that have reference to the table and &lt;code&gt;:public&lt;/code&gt; access tables allow read plus write operations to all the processes with reference. Since all the messages to GenServers or Agents are serialized, meaning all operations on them are performed one at a time, &lt;em&gt;(heeding to the Actor model of concurrency computation)&lt;/em&gt; ETS tables come to rescue to enable concurrent data access and manipulation across processes.&lt;/p&gt;

&lt;p&gt;ETS tables can also store Elixir structs, so objects like &lt;code&gt;%User{id: 1}&lt;/code&gt; do not need to be deserialized after retrieval.&lt;/p&gt;

&lt;h2&gt;
  
  
  ETS and table management
&lt;/h2&gt;

&lt;p&gt;More often than not, you'd store sensitive data in your ETS tables and would not want that data to be wiped out with unexpected process terminations. For that, ETS can shed ownership in the event of owner process crash and delegate itself to the &lt;code&gt;heir&lt;/code&gt; process declared at the time of initialization.&lt;/p&gt;

&lt;p&gt;Loss of ETS tables can have widespread implications if they are not architected right. The advantages of ETS tables of providing optimizations on concurrent data access would be lost if processes that depend on that data terminate because of errors in the owner process, with no fault of their own. It's easier and more manageable to architect your ETS tables in a manner that it doesn't necessarily always need &lt;code&gt;heir&lt;/code&gt; processes to be more resilient.&lt;/p&gt;

&lt;p&gt;Since the &lt;code&gt;public&lt;/code&gt; access ETS tables allow reads and writes across application processes you might be tempted to create ETS tables way higher up in supervision/process tree hierarchy than it needs to be in. It will technically work, but it's not worth the developer confusion and data lifecycle as you'd not want data to live forever till application restart. You should rather think in terms of data-localisation when architecting processes that own ETS tables. Processes that access data via ETS tables should be co-located and by virtue, should be restarted together.&lt;/p&gt;

&lt;p&gt;A good approach could be to have a dedicated GenServer for the ETS initialization. Since, it won't don't do much more than owning the table; it's less prone to die out of computational errors. Another approach can be to initialize ETS tables in the Supervisor and drill its reference down to its child processes. In this scenario, if your Supervisor dies with reasonable restart threshold and strategy for its children before it terminates itself, it should be fine for the ETS table to be restarted with it.&lt;/p&gt;

&lt;p&gt;It's pretty straightforward to trap exit of a process and delegate the ETS table to an &lt;code&gt;heir&lt;/code&gt; process, you can get the syntax and insight on the topic in this post - &lt;a href="http://steve.vinoski.net/blog/2011/03/23/dont-lose-your-ets-tables/" rel="noopener noreferrer"&gt;Don't lose your ETS tables&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  ETS in production
&lt;/h2&gt;

&lt;p&gt;ETS tables are a type of data store and not a database, so there's limited support for transactions - the last write wins, although the writes are atomic and isolated. To implement pseudo-transactional writes for a table, the write operations need to be behind a process, evaluating each mailbox's write message to the table one at a time. For proper transaction support, you should look into Mnesia before integrating PostgreSQL or MySQL.&lt;/p&gt;

&lt;p&gt;You might find the use case where you'd need to iterate over all the rows in an ETS table to report to a process or an external service. You can use &lt;code&gt;tab2list/1&lt;/code&gt;, &lt;code&gt;foldl/3&lt;/code&gt;, or &lt;code&gt;foldr/3&lt;/code&gt; functions for this. While &lt;code&gt;tab2list/1&lt;/code&gt; returns the dump of ETS data reduced in a list, &lt;code&gt;foldl/3&lt;/code&gt; and &lt;code&gt;foldr/3&lt;/code&gt; are essentially &lt;em&gt;reduce&lt;/em&gt; functions and you can design the accumulated data. There are other functions like &lt;code&gt;first/1&lt;/code&gt;, &lt;code&gt;next/2&lt;/code&gt;, &lt;code&gt;prev/2&lt;/code&gt; and &lt;code&gt;last/1&lt;/code&gt; to iterate over tables, but these operations are not isolated and do not guarantee &lt;a href="https://erlang.org/doc/man/ets.html#table-traversal" rel="noopener noreferrer"&gt;safe travel&lt;/a&gt; of tables &lt;em&gt;(meaning objects can be updated/removed/added concurrently while the iteration job is processing)&lt;/em&gt; and could introduce side-effects when not used alongside &lt;code&gt;safe_fixtable/2&lt;/code&gt; function unlike &lt;code&gt;foldl/3&lt;/code&gt;, &lt;code&gt;foldr/3&lt;/code&gt; and &lt;code&gt;tab2list/1&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If you do have a use case of iterating over ETS tables, you'd want to shard your ETS tables for scalability and by association, processes. You can use &lt;code&gt;Swarm&lt;/code&gt; or &lt;code&gt;pg2&lt;/code&gt; to register these processes to a group and trigger group calls which then have to iterate over smaller sets of data. Since these group calls will trigger concurrent iterations with smaller datasets to cover, it'd be much faster to iterate over iterating a giant ETS table.&lt;/p&gt;

&lt;p&gt;In ETS, when you do a lookup to get the associated object to the key specified in the table, that value is copied over to the heap of the process. Which has minor to no implications for most use cases, but if your object is significantly large that it incurs penalty on copying amongst processes, you'd want to take a look at &lt;a href="http://erlang.org/doc/man/persistent_term.html" rel="noopener noreferrer"&gt;&lt;code&gt;persistent_term&lt;/code&gt;&lt;/a&gt;. The module is similar to ETS in that it provides storage for Erlang terms that can be accessed in constant time, but with the difference that &lt;code&gt;persistent_term&lt;/code&gt; has been highly optimized for reading terms at the expense of writing and updating terms.&lt;/p&gt;

&lt;p&gt;For debugging ETS tables in production, you can use &lt;code&gt;Observer&lt;/code&gt; tool provided by OTP to inspect your table's data. You can connect remotely to the production node and see ETS data in a UI out of the box.&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%2Fi%2F5aazgy8k7gi70vgf90hm.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%2Fi%2F5aazgy8k7gi70vgf90hm.png" alt="ETS in Observer"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can get data you'd get from &lt;code&gt;:ets.info/1&lt;/code&gt; for your ETS tables in the Observer in production as well.&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%2Fi%2Fynd1ypz7ytn9p9v7qc3p.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%2Fi%2Fynd1ypz7ytn9p9v7qc3p.png" alt="ETS info in Observer"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Although, the table viewer doesn't provide write access to the tables; Observer do provide the means to kill the owner process. Be very careful when debugging in production as production can easily go down due to unintended actions in Observer.&lt;/p&gt;

&lt;p&gt;Another unpopular and very dangerous way to debug large ETS tables, would be of starting a shell inside the release directory and running &lt;code&gt;&amp;lt;release_dir&amp;gt;/bin/yourapp remote_console&lt;/code&gt;. This gives you the IEx shell inside the running remote server process. This way, your deployments are no black box, you interact with it the same way you would with a local development environment. You can insert, update, and delete ETS objects in production with this.&lt;br&gt;
I would recommend you not to use this method in production unless you really know what you're doing.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Hope this post has been helpful in introducing and dealing with ETS tables in your systems going forward&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Subscribe to the newsletter for early updates, I usually cross-post my articles a little later here - &lt;a href="https://sayan.substack.com" rel="noopener noreferrer"&gt;Link&lt;/a&gt;&lt;/em&gt; :)&lt;/p&gt;

</description>
      <category>elixir</category>
      <category>erlang</category>
      <category>distributedsystems</category>
      <category>database</category>
    </item>
  </channel>
</rss>
