<turbo-stream action="append" target="posts_list"><template>    <div class="postbit" id="333881" data-post-id="333881">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="benwilson512" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/benwilson512/120/1457_2.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  benwilson512
                  </h3>
		          </div>
						
			          <div class="user-title">
									<span>Author of Craft GraphQL APIs in Elixir with Absinthe</span>
			          </div>
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<p>A table lock will work if you aren’t allowed to use a unique index. Without insight into why a client might “forbid” such an index though it’s hard to comment further, that is such a strange choice. As I noted, whether you use a table lock or not you still probably want an index to avoid a table scan. At that point adding <code>unique</code> is literally free. If you are OK with table scans and don’t mind locking the whole table for inserts while you do the table scan then probably we’re back to 0 scale.</p>
<p>EDIT: Notably, a table lock is ONLY going to work if EVERY code path which inserts takes this lock and verifies. It provides no guarantees of the data “at rest” and so it is quite susceptible to bugs, or even just gaps in reasoning where you forget that in addition to the normal code path there is also some other path which forgets to make this check.</p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="333881" data-batch-url="/posts/batch_likers">
                        1
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/how-to-do-get-or-insert-by-in-ecto-without-race-condition/22160/32">Post #31</a>
	                </div>
	            </div>
              <div id="likers-container-333881" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="333881"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #31"></div>
  </section>
</div>
    <div class="postbit" id="333883" data-post-id="333883">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="tfwright" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/tfwright/120/20299_2.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  tfwright
                  </h3>
		          </div>
						
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<p>My case was a table containing data about locations, including lat/long. Client wanted to avoid adding duplicates, and so requested we support upserting on lat/long. The problem with this is that lat/long have varying levels of specificity and so while they worked great for plotting the locations on the map, we couldn’t actually know that a duplicate pair equate to the same physical location. I strongly urged we enforce uniqueness and choose a level of specificity to require, despite the fact it would involve some data migration headaches. Client just said no. Now that I’ve typed this out, I’m realizing how much of an edge case this truly was and is more a business problem than a technical one. In my defense I began by strongly urging the use of an index as well <img src="https://forum.elixirforum.com/images/emoji/apple/slight_smile.png?v=15" title=":slight_smile:" class="emoji" alt=":slight_smile:" loading="lazy" width="20" height="20"></p>
<p>edit: and of course the fields were still indexed for lookups</p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="333883" data-batch-url="/posts/batch_likers">
                        2
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/how-to-do-get-or-insert-by-in-ecto-without-race-condition/22160/33">Post #32</a>
	                </div>
	            </div>
              <div id="likers-container-333883" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="333883"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #32"></div>
  </section>
</div>
    <div class="postbit" id="333902" data-post-id="333902">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="garrison" src="/assets/icons/user-9f439610.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  garrison
                  </h3>
		          </div>
						
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<aside class="quote no-group" data-username="tfwright" data-post="33" data-topic="22160">
<div class="title">
<div class="quote-controls"></div>
<img alt="" width="24" height="24" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/tfwright/48/20299_2.png" class="avatar"> tfwright:</div>
<blockquote>
<p>The problem with this is that lat/long have varying levels of specificity and so while they worked great for plotting the locations on the map, we couldn’t actually know that a duplicate pair equate to the same physical location.</p>
</blockquote>
</aside>
<p>This is one of those complex situations where just using SERIALIZABLE can be a good solution.</p>
<aside class="quote no-group" data-username="benwilson512" data-post="28" data-topic="22160">
<div class="title">
<div class="quote-controls"></div>
<img alt="" width="24" height="24" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/benwilson512/48/1457_2.png" class="avatar"> benwilson512:</div>
<blockquote>
<p>You’re only allowing a single reader / writer to the table at a time (at least, for any reader / writer that has to perform that sort of operation).</p>
</blockquote>
</aside>
<p>To elaborate a bit about the table locks: by taking a lock on the entire table every time, you’re forcibly serializing <em>all</em> of the transactions taking that lock (plus anything touching that table, too). There are some databases (e.g. <a href="https://sqlite.org/isolation.html" rel="noopener nofollow ugc">SQLite</a>) which do, by design, serialize all transactions (and therefore have no write concurrency).</p>
<p>This is okay in SQLite because the database is literally in-process and there is effectively no latency. But with a postgres server, you probably have at least a few milliseconds of latency times at least a few round trips for an interactive transaction. During that time, all of the other transactions are blocked, so your throughput just gets obliterated.</p>
<p>(Also, for the record, SQLite’s transactional throughput is abysmal anyway. It’s just not meant for that.)</p>
<p>Depending on the task you could distribute the locks more evenly to recover some concurrency. For example, if the lat/long had to be unique <em>per user</em>, you could take a lock on the <em>user</em> (or on a row for that user in a table created for that purpose). Then at least multiple users could perform inserts simultaneously.</p>
<p>Also, I’d like to correct something I said earlier:</p>
<aside class="quote no-group" data-username="garrison" data-post="17" data-topic="22160">
<div class="title">
<div class="quote-controls"></div>
<img alt="" width="24" height="24" src="https://forum.elixirforum.com/letter_avatar_proxy/v4/letter/g/3bc359/48.png" class="avatar"> garrison:</div>
<blockquote>
<p>I <em>think</em> the only way to get a truly atomic operation is to do with Jose suggested above and use an insert/on_conflict/returning with a unique constraint.</p>
</blockquote>
</aside>
<p>This is actually not true, and Jose’s post is wrong (I knew this but forgot). <a href="https://www.postgresql.org/docs/current/sql-insert.html" rel="noopener nofollow ugc">Postgres does not return the existing row in an upsert if no update takes place</a> (see the bit about <code>RETURNING</code> under “Description”). You could get around this by performing a token update (e.g. <code>SET value=EXCLUDED.value</code>), but this is a very bad idea because postgres’s ancient MVCC implementation writes a new row every time you do this (even if you change nothing). If you run <code>get_or_insert</code> a lot, which is usually the case with such a function, this will degrade performance (postgres has to gc those rows, and also it stores said rows oldest to newest because, again, ancient MVCC implementation).</p>
<p>Unfortunately I <em>think</em> this means that it’s literally impossible to perform an atomic <code>get_or_insert</code> outside of SERIALIZABLE. Of course, in practice errors due to conflicts will be rare, so this hardly matters.</p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="333902" data-batch-url="/posts/batch_likers">
                        1
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/how-to-do-get-or-insert-by-in-ecto-without-race-condition/22160/34">Post #33</a>
	                </div>
	            </div>
              <div id="likers-container-333902" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="333902"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #33"></div>
  </section>
</div>
    <div class="postbit" id="333903" data-post-id="333903">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="benwilson512" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/benwilson512/120/1457_2.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  benwilson512
                  </h3>
		          </div>
						
			          <div class="user-title">
									<span>Author of Craft GraphQL APIs in Elixir with Absinthe</span>
			          </div>
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<p>Yea at my company we have a “get or insert or get” for this for when we don’t tend to have deletes. You do a get, and if it doesn’t exist you try to insert, returning the ID. If you don’t get an ID there was a race and you lost, so get again. Doesn’t work for every access pattern but if you generally expect that rows already exist it’s nice.</p>
<p>A note on SERIALIZABLE: it only conflicts with other transactions that are also SERIALIZABLE. This can be a gotcha because if you call the code in a transaction that has not been set to that level then it can allow inconsistencies. It can also be difficult to compose into other operations since now EVERY operation in the transaction is impacted not just the one you’re doing on that table.</p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="333903" data-batch-url="/posts/batch_likers">
                        2
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/how-to-do-get-or-insert-by-in-ecto-without-race-condition/22160/35">Post #34</a>
	                </div>
	            </div>
              <div id="likers-container-333903" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="333903"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-last-post cat-last-post" title="Last post!"></div>
  </section>
</div>
</template></turbo-stream><turbo-stream action="replace" target="load-more-container"><template><div id="load-more-container" class="load-more-container">
    <span class="all-loaded">— All posts loaded —</span>
</div></template></turbo-stream>