<?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: Alesis Manzano</title>
    <description>The latest articles on DEV Community by Alesis Manzano (@alesisjoan).</description>
    <link>https://dev.to/alesisjoan</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%2F74091%2Fc07ac57f-c0f3-4a4a-a9f1-e6da25cf736c.jpeg</url>
      <title>DEV Community: Alesis Manzano</title>
      <link>https://dev.to/alesisjoan</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/alesisjoan"/>
    <language>en</language>
    <item>
      <title>How I Saved 2.7GB of Memory in Odoo by Skipping the ORM</title>
      <dc:creator>Alesis Manzano</dc:creator>
      <pubDate>Fri, 11 Jul 2025 16:59:03 +0000</pubDate>
      <link>https://dev.to/alesisjoan/how-i-saved-27gb-of-memory-in-odoo-by-skipping-the-orm-828</link>
      <guid>https://dev.to/alesisjoan/how-i-saved-27gb-of-memory-in-odoo-by-skipping-the-orm-828</guid>
      <description>&lt;h1&gt;
  
  
  How I Saved 2.7GB of Memory in Odoo by Skipping the ORM
&lt;/h1&gt;

&lt;p&gt;I recently needed to process &lt;strong&gt;400,000 accounting lines&lt;/strong&gt; in Odoo. While experimenting with memory profiling, I ran a benchmark on a subset of &lt;strong&gt;10,000 records&lt;/strong&gt;—and the results were eye-opening.&lt;/p&gt;




&lt;h2&gt;
  
  
  🧪 Benchmark Results (on 10k records)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pympler&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;asizeof&lt;/span&gt;

&lt;span class="c1"&gt;# Classic ORM usage
&lt;/span&gt;&lt;span class="n"&gt;classic&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;account.move.line&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;([],&lt;/span&gt; &lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;classic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mapped&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# forces read
&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Classic:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;asizeof&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;asizeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;classic&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;MB&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# ~70MB
&lt;/span&gt;
&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;invalidate_all&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# ORM without prefetching
&lt;/span&gt;&lt;span class="n"&gt;light&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;account.move.line&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;with_context&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prefetch_fields&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;([],&lt;/span&gt; &lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;light&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mapped&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;No prefetch:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;asizeof&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;asizeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;light&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;MB&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# ~25MB
&lt;/span&gt;
&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;invalidate_all&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# Raw SQL via cursor
&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    SELECT name FROM account_move_line
    ORDER BY date DESC, move_name DESC, id
    LIMIT 10000
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;rows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetchall&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Raw tuples:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;asizeof&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;asizeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;MB&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# ~1.5MB
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  🤔 What This Really Means
&lt;/h2&gt;

&lt;p&gt;These results are not just numbers—they reveal something deeper about how Odoo loads and stores data:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The &lt;strong&gt;ORM is convenient&lt;/strong&gt;, but every bit of convenience comes at a cost: memory, latency, and object complexity.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Even without prefetching, Odoo still builds full recordsets with metadata, methods, and context.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The &lt;strong&gt;raw SQL result (a list of tuples)&lt;/strong&gt; is just plain data. That simplicity is what makes it powerful and memory-friendly.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When you're &lt;strong&gt;just reading values&lt;/strong&gt;, especially at scale, the ORM becomes a luxury you might not need.&lt;/p&gt;

&lt;h2&gt;
  
  
  🧮 Extrapolated for 400,000 records
&lt;/h2&gt;

&lt;p&gt;MethodEst. Memory Use (400k)CommentsClassic ORM~2.8 GBLoads related fields, expensiveNo Prefetch~1.0 GBLighter but still bulkyRaw SQL (tuples)~60 MB✅ &lt;strong&gt;Leanest and fastest&lt;/strong&gt; option&lt;/p&gt;

&lt;p&gt;That’s a &lt;strong&gt;2.74GB saving&lt;/strong&gt; with one simple change.&lt;/p&gt;

&lt;h2&gt;
  
  
  ✅ When to Use This
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;You're processing &lt;strong&gt;hundreds of thousands&lt;/strong&gt; of records&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You don’t need ORM features (onchange, access rights, tracking)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You're doing &lt;strong&gt;read-only&lt;/strong&gt; operations (exports, reports, analytics)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  ⚠️ Caveats
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;You lose the power of fields and record methods&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You’re responsible for data validation and types&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;It’s not suitable when modifying or creating records&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  🧠 Final Thoughts
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;You don’t always need to go lower-level.But when you do—&lt;strong&gt;the gains can be massive&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is a reminder that &lt;strong&gt;ORMs are abstraction layers&lt;/strong&gt;, not magic. When you know what’s under the hood, you can choose the right tool for each job.&lt;/p&gt;

&lt;p&gt;Whether you're building an export, a batch job, or a one-off script—&lt;strong&gt;be mindful of memory.&lt;/strong&gt; It might just save you from crashes, slowdowns, and server pain.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;If you enjoyed this insight, drop a comment or share your own optimization tricks!&lt;/em&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  odoo #python #performance #memory #optimization #sql #devops
&lt;/h1&gt;

</description>
      <category>odoo</category>
      <category>orm</category>
      <category>cache</category>
      <category>prefetch</category>
    </item>
    <item>
      <title>My experience with MercadoLibre's Mutant challenge</title>
      <dc:creator>Alesis Manzano</dc:creator>
      <pubDate>Wed, 27 Nov 2019 15:33:33 +0000</pubDate>
      <link>https://dev.to/alesisjoan/my-experience-with-mercadolibre-s-mutant-challenge-52n4</link>
      <guid>https://dev.to/alesisjoan/my-experience-with-mercadolibre-s-mutant-challenge-52n4</guid>
      <description>&lt;h1&gt;Introduction&lt;/h1&gt;

&lt;p&gt;The technical interview consists of developing an api so that Magneto can recruit mutants. The api receives an array of words that represent human DNA. If 4 equal letters  (A, T, G, C) were found, then a mutation was detected.&lt;/p&gt;

&lt;p&gt;The challenge had lot of goals but few time (no more than 30hs) to do it: OK!&lt;/p&gt;

&lt;h2&gt;Goals&lt;/h2&gt;

&lt;p&gt;Explicit requirements.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Effective: You have to detect if a human is a mutant or not&lt;/li&gt;
&lt;li&gt;Efficient: make the algorithm that can support a million requests per second&lt;/li&gt;
&lt;li&gt;Time: do it in no more than a week. Consider that I have to do it after work, so it must be done within 30hs.&lt;/li&gt;
&lt;li&gt;Testing coverage &amp;gt; 80%&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Generic requirements.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Human Readable Code: write a clean code with good practices, comment if necessary, etc.&lt;/li&gt;
&lt;li&gt;Use a cloud framework: AWS, Google, Heroku&lt;/li&gt;
&lt;li&gt;Load to github&lt;/li&gt;
&lt;li&gt;Make README and extra documentation&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;Before coding&lt;/h2&gt;

&lt;p&gt;I've started reading the pdf and thinking about what the structure should be (early return, max lengths, regex?) and the framework to use, wether Spring or Python Flask. As loading the project to github was a requirement, I search for projects like that and scratching ideas. Always considering that I have few time to do it and need to priorize my "the program should be" list. &lt;/p&gt;

&lt;p&gt;Choose the framework: JAVA or Python ?&lt;/p&gt;

&lt;p&gt;JAVA and Python are the two frameworks I'm familiar with, more with Python than JAVA. JAVA Spring is a good choice as many web projects are made with it and has an indisputable matureness.&lt;br&gt;
Searching in the projects I realized a project was done with python, so I made the choice. Why python? Since I've been working mainly with python i feel comfortable with it (o, I'm used to it) because is fast coding and has an acceptable performance. I know it is used for bigdata and science. Python also is more "flexible" than JAVA. Another reasonThen I choose heroku for the cloud server because it is fast to create an app. For now, no DB-persist framework is needed.&lt;/p&gt;

&lt;h2&gt;No more thoughts: start to CODING :D&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Early Return. It was not an explicit requirement but I considered essential. In my opinion, you can make an efficient algorithm, avoiding O(n^n) notations, embebbed loops, applying machine learning (no time for researching them). However there ir a classic human behaving that it is always present: bad entry. So early return is very important. The function it must detect bad entries as like words are non-empty and have DNA letters (A, T, G, C), the matrix has a correct order (NxN). I should check if the matrix has more than x order? An 1000n order could make the system to collapse (finally I had no information about that). Another thing is that less than 1% of humans are mutant and &amp;gt; 99% are not mutant, so it is possible that the api could be accessed by mostly human. What a pity, I could not early detect if it is human until I detect before is not a mutant. Finally if I detect it is a mutant, break the loop.&lt;/li&gt;

&lt;li&gt;Checking mutant word: regex, a list of characters in a word, binary-search or tree-structures, AI searching methods. I consider that regex is more flexible and good practice that hardcoding the list of possible words (AAAA, CCCC, TTTT, GGGG). I realised that it is not expected that they couldn't find another letter for mutant DNA, or another sequence (e.g. TATA): no flexible is needed, just efficient. No time for the last methods. So I choose for hardcoding the 4 sequences within an if.&lt;/li&gt;

&lt;li&gt;Reading the matrix strategies: in the projects I found that there were 3 strategies to read the matrix. From left to right for every row. From bottom to top, for every column. And every diagonal. For that, I need to break the matrix in arrays of chars, so it was adding complex to the algorithm. So that, I choose to have faith and first check the matrix horizontally. If I were lucky, I could detect a mutant in the rows. If not, check in the columns. Finally if needed, break the matrix in arrays of char so that could check the diagonals. In other words, start with simple scenario and leave the complex ones to the end.&lt;/li&gt;

&lt;li&gt;Persist the result for repeating entries. This is related to early return. Consider if a human insist on checking again (bad intentionally or not), that could waste resources. So I consider a good practice to save the result for the entry. I used Redis for that. It costs much less than reprocessing the DNA matrix again.&lt;/li&gt;

&lt;li&gt;Notify the result to the user? As reaching a first version, I realised if were a good choice to notify a human if it is false result. The requeriment was return 200 if were a mutant and 403 if were a human. First, 200 means that the server could process the requests and 403 is a forbidden access; for api-rest 403 could be interpreted as an access error. I think it should answer 200 in both cases. Second, sometimes it is not good to give extra information to the user, the program were intended to recruit mutants. Because if was my interpretation of the situation, I ask for MercadoLibre recruiters about that. They answered that do what I feel to. So I keep the requerimient: 200 if mutant, 403 if human&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;After coding&lt;/h2&gt;

&lt;p&gt;I did the test for the program and fix minor things. I detected that I had some false-positive cases and some false-negative too. &lt;/p&gt;

&lt;p&gt;Another thing I had to deal is using heroku, thank good-ness was not a big problem, but I took me 6hs (2 days).&lt;/p&gt;

&lt;h2&gt;To Do&lt;/h2&gt;

&lt;p&gt;I really wanted to do a better program but had little time for the interview.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Work with algebra's transformation on matrix: inverted matrix, transpose, etc.&lt;/li&gt;
&lt;li&gt;Searching methods like binary search, tree, AI methods...&lt;/li&gt;
&lt;li&gt;Use some load balancer like Nginx&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;What I learned&lt;/h2&gt;

&lt;p&gt;Testing code is more important than I knew.&lt;br&gt;
Choose a good framework that you are confortable with is a good decission.&lt;br&gt;
Write your experiences and share! I started the project seeing other projects and save me a lot of time.&lt;/p&gt;

&lt;p&gt;my code is located &lt;a href="https://github.com/alesisjoan/meli-mutant"&gt;https://github.com/alesisjoan/meli-mutant&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Hope it helped you.&lt;/p&gt;

</description>
      <category>meli</category>
      <category>mercadolibre</category>
      <category>backend</category>
      <category>python</category>
    </item>
  </channel>
</rss>
