<?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: Julian Pineda</title>
    <description>The latest articles on DEV Community by Julian Pineda (@julianpz21).</description>
    <link>https://dev.to/julianpz21</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%2F855093%2Fb32602f5-cdc3-40df-a0db-ce4864f99de9.jpeg</url>
      <title>DEV Community: Julian Pineda</title>
      <link>https://dev.to/julianpz21</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/julianpz21"/>
    <language>en</language>
    <item>
      <title>Optimiza tu aplicación rails utilizando índices en Postgres</title>
      <dc:creator>Julian Pineda</dc:creator>
      <pubDate>Sat, 30 Apr 2022 00:12:56 +0000</pubDate>
      <link>https://dev.to/ayenda/optimiza-tu-aplicacion-rails-utilizando-indices-en-postgres-5da3</link>
      <guid>https://dev.to/ayenda/optimiza-tu-aplicacion-rails-utilizando-indices-en-postgres-5da3</guid>
      <description>&lt;p&gt;Algunos de los factores más comunes y que más impactan el “performance” de una aplicación están relacionados con las consultas en base de datos muy grandes, por lo tanto, entender cómo detectar y solucionar estos problemas es de gran relevancia en cualquier equipo de desarrollo backend. &lt;/p&gt;

&lt;p&gt;Recientemente en Ayenda se detectó que una consulta a una tabla de nuestra base de datos estaba tardando demasiado tiempo en ser procesada, la consulta se hacía a una tabla con millones de registros y tardaba alrededor de 7.8 segundos en ser procesada en un entorno de desarrollo local.&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%2F3km7p5jxllzwmr9qvq90.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%2F3km7p5jxllzwmr9qvq90.png" alt="consulta en base de datos usando EXPLAIN ANALYZE"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Existen diferentes estrategias para abordar la solución a este problema, una de ellas y la implementada en este caso,  es el uso de índices en la base de datos. Por tal motivo, desde el equipo de Tech en Ayenda queremos compartirles este blog post donde hablaremos de la definición, creación y aplicación de algunos tipos de índices en PostgreSQL bajo una aplicación construida con Ruby on Rails. &lt;/p&gt;

&lt;h4&gt;
  
  
  Temas tratados en el blog:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Indexación en bases de datos.&lt;/li&gt;
&lt;li&gt;Definición de un índice en una base de datos.&lt;/li&gt;
&lt;li&gt;Índice B-Tree&lt;/li&gt;
&lt;li&gt;Índice GIN (Generalized Inverted Indexes).&lt;/li&gt;
&lt;li&gt;Creación de índices en Ruby on Rails. &lt;/li&gt;
&lt;li&gt;Uso del índice B-Tree.&lt;/li&gt;
&lt;li&gt;Creación y uso de índice GIN en Ruby on Rails&lt;/li&gt;
&lt;li&gt;Índices multicolumna&lt;/li&gt;
&lt;li&gt;Combinando multiples índices&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Indexación en bases de datos.
&lt;/h4&gt;

&lt;p&gt;Supongamos que queremos encontrar un usuario con un número de identificación específico en nuestra base de datos. Utilizando &lt;em&gt;ActiveRecord&lt;/em&gt; podríamos ejecutar un query como el siguiente.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User.where(id_number: 123456789)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cuando ejecutamos el query anterior, por lo general, PostgreSQL hará un escaneo secuencial para leer todas las filas de la tabla &lt;em&gt;User&lt;/em&gt; buscando algún registro donde la columna &lt;em&gt;id_number&lt;/em&gt; sea igual al número &lt;em&gt;123456789&lt;/em&gt;. Esto puede convertirse en un problema cuando la consulta se hace en una tabla que contiene millones de datos, debido a que el sistema tiene que buscar en toda la tabla para encontrar un registro dado, lo que puede impactar fuertemente en el perfomance de la base de datos. Sin embargo, al agregar un índice a la columna de &lt;em&gt;id_number&lt;/em&gt;, PostgreSQL podrá realizar el query utilizando el índice creado para hacer un escaneo de la tabla mucho más eficiente.&lt;/p&gt;

&lt;p&gt;A diferencia de un escaneo secuencial, un escaneo por índice es mucho más rápido, ya que en lugar de hacer una consulta donde el tiempo de ejecución está relacionado linealmente con la cantidad de datos &lt;em&gt;‘O(n)’&lt;/em&gt;, se hará un consulta con un performance logarítmico &lt;em&gt;‘O(log n)’&lt;/em&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Definición de un índice en una base de datos (DB).
&lt;/h4&gt;

&lt;p&gt;Un índice, en palabras simples, es una estructura de datos que se define sobre una o varias columnas y permite ubicar de forma eficiente un registro determinado en una tabla. Una vez creado un índice, el sistema lo actualizará cuando se modifique la tabla y lo utilizará en consultas determinadas, por lo tanto, los índices aumentan la velocidad en las consultas solo para las instrucciones &lt;em&gt;SELECT&lt;/em&gt;, &lt;em&gt;UPDATE&lt;/em&gt; y &lt;em&gt;DELETE&lt;/em&gt;, no en comandos de inserción &lt;em&gt;CREATE&lt;/em&gt;. Adicionalmente un índice por definición debe ocupar un espacio en el disco duro, donde almacenará la información de su estructura de datos. Existen diferentes tipos de índices y cada uno utiliza un algoritmo particular que se adapta mejor al tipo de dato y al tipo de consulta. Actualmente PostgreSQL soporta varios tipos de índices, algunos de ellos son: &lt;em&gt;B-tree&lt;/em&gt;, &lt;em&gt;Hash&lt;/em&gt;, &lt;em&gt;GiST&lt;/em&gt; y &lt;em&gt;GIN&lt;/em&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Índice B-Tree
&lt;/h4&gt;

&lt;p&gt;El índice &lt;em&gt;B-Tree&lt;/em&gt; es el más utilizado debido a su versatilidad y además es el índice creado por defecto en PostgreSQL cuando no se especifica el tipo. &lt;em&gt;B-Tree&lt;/em&gt; es un tipo especial de estructura de datos de tipo árbol autobalanceado en el que cada nodo puede contener más de una clave y  más de dos nodos hijos. Este tipo de índice es utilizado comúnmente en datos de tipo numérico, fecha/hora y de tipo carácter (con algunas limitaciones). La siguiente imagen muestra la estructura de datos para un índice &lt;em&gt;B-Tree&lt;/em&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%2Feoonr4z5mqz5cgek7829.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%2Feoonr4z5mqz5cgek7829.png" alt="Estructura índice b-tree"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Por lo general PostgreSQL considerará utilizar el índice &lt;em&gt;B-Tree&lt;/em&gt; siempre que una columna indexada esté involucrada en una consulta que utilice alguno de los siguientes operadores: &lt;code&gt;(&amp;lt;, &amp;lt;=, =, &amp;gt;=, &amp;gt;)&lt;/code&gt;, &lt;em&gt;BETWEEN&lt;/em&gt;, &lt;em&gt;IN&lt;/em&gt; y condiciones como &lt;em&gt;IS NULL&lt;/em&gt; o &lt;em&gt;IS NOT NULL&lt;/em&gt;. PostgreSQL también considera utilizar este tipo de índice para consultas que involucran operadores de patrones de coincidencia como &lt;em&gt;LIKE&lt;/em&gt; y &lt;em&gt;~&lt;/em&gt; siempre y cuando el patrón sea una constante y esté en el comienzo de la cadena, por ejemplo, &lt;em&gt;LIKE 'foo%'&lt;/em&gt; o &lt;em&gt;~ '^ foo'&lt;/em&gt;, pero nunca para &lt;em&gt;LIKE '%foo'&lt;/em&gt;. Por último también es posible utilizarlo con los operadores &lt;em&gt;ILIKE&lt;/em&gt; y &lt;em&gt;~*&lt;/em&gt;, pero solo si el patrón comienza con caracteres no alfabéticos, es decir, caracteres que no son afectados por conversión de mayúsculas o minúsculas.&lt;/p&gt;

&lt;h4&gt;
  
  
  Índice GIN (Generalized Inverted Indexes).
&lt;/h4&gt;

&lt;p&gt;El índice &lt;em&gt;GIN&lt;/em&gt; es utilizado en tipos de datos que almacenan información estructurada como arrays, jsonb o hstore, adicionalmente se utiliza con frecuencia en  implementaciones para optimizar búsquedas de texto. Este índice almacena un conjunto de pares (clave, lista), donde la lista es un conjunto de IDs de las filas donde aparece la clave. Un ID de una lista puede aparecer en otras listas, ya que un elemento puede contener más de una clave. Cada clave se almacena sólo una vez, por lo que el índice &lt;em&gt;GIN&lt;/em&gt; es muy compacto para los casos en que la misma clave aparece muchas veces en los registros. Un ejemplo de la estructura de datos para un índice &lt;em&gt;GIN&lt;/em&gt; se muestra a continuación.&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%2F4wj6cg80fmpxzcdjekcz.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%2F4wj6cg80fmpxzcdjekcz.png" alt="Estructura índice GIN"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Este tipo de índices admite diferentes estrategias de indexación para cada tipo de dato, por ende, los operadores con los que se puede usar este índice varían según la estrategia de indexación, por ejemplo, la implementación de &lt;em&gt;GIN&lt;/em&gt; por defecto en PostgreSQL para un array admite consultas con los siguientes operadores &lt;code&gt;(&amp;lt;@, @&amp;gt;, =, &amp;amp;&amp;amp;)&lt;/code&gt;. Ejemplos: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;[‘a’, ‘b’, ‘c’] @&amp;gt; [‘a’, ‘b’, ‘b’, ‘c’] evalúa si [‘a’, ‘b’, ‘c’] contiene a [‘a’, ‘b’, ‘b’, ‘c’] (verdadero)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;[1, 1, 4] &amp;lt;@ [4, 3, 2, 1] evalúa si [1, 1, 4] es contenido por [4, 3, 2, 1] (verdadero)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;[1, 2] &amp;amp;&amp;amp; [2, 3] evalúa si los dos array se superponen  (verdadero)&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Creación de índices en Ruby on Rails.
&lt;/h4&gt;

&lt;p&gt;Ahora bien, Ruby on Rails nos brinda la capacidad gestionar la creación de índices mediante migraciones con &lt;em&gt;ActiveRecord&lt;/em&gt;, por ejemplo, con la siguiente instrucción podemos crear la tabla &lt;em&gt;IntegerTable&lt;/em&gt; con dos columnas de tipo entero (&lt;em&gt;integer_a&lt;/em&gt;, &lt;em&gt;integer_b&lt;/em&gt;) y con un índice para la columna &lt;em&gt;integer_b&lt;/em&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rails g model IntegerTable integer_a:integer integer_b:integer:index
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;La instrucción anterior genera la migración que se muestra a continuación, donde podemos observar el método &lt;em&gt;add_index&lt;/em&gt; encargado de generar el índice a la tabla y columna correspondiente. Si no se especifica el tipo de índice se crea por default un índice tipo &lt;em&gt;B-Tree&lt;/em&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class CreateIntegerTables &amp;lt; ActiveRecord::Migration[7.0]
  def change
    create_table :integer_tables do |t|
      t.integer :integer_a
      t.integer :integer_b

      t.timestamps
    end
    add_index :integer_tables, :integer_b
  end
end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Uso del índice B-Tree.
&lt;/h3&gt;

&lt;p&gt;Para analizar el uso de los índices &lt;em&gt;B-Tree&lt;/em&gt; en diferentes tipos de datos utilizaremos algunas tablas y columnas con información creada previamente. Cada tabla tiene dos columnas del mismo tipo y con la misma información, sin embargo una de las columnas estará indexada con &lt;em&gt;B-tree&lt;/em&gt; y la otra no. En los ejemplos utilizados a continuación contamos con alrededor de un millón de registros aleatorios para cada tabla. Para observar los resultados utilizaremos el IDE, DBeaver Community Edition (&lt;a href="https://dbeaver.io/" rel="noopener noreferrer"&gt;https://dbeaver.io/&lt;/a&gt;) y el comando &lt;em&gt;EXPLAIN ANALYZE&lt;/em&gt; en cada query para conocer los detalles de las consultas ejecutadas. En el siguiente repositorio se encuentra el proyecto utilizado para todas la consultas mostradas en este blog: (&lt;a href="https://github.com/julianpz21/database_indexes" rel="noopener noreferrer"&gt;https://github.com/julianpz21/database_indexes&lt;/a&gt;)  &lt;/p&gt;

&lt;h4&gt;
  
  
  Índice B-Tree en columnas tipo números.
&lt;/h4&gt;

&lt;p&gt;Analicemos el comportamiento de dos consultas realizadas a la tabla &lt;em&gt;Integer_tables&lt;/em&gt;, la primera consulta se realiza sobre la  columna sin indexar (&lt;em&gt;integer_a&lt;/em&gt;) y la segunda consulta sobre la columna indexada mediante &lt;em&gt;B-tree&lt;/em&gt; (&lt;em&gt;integer_b&lt;/em&gt;). En las siguientes figuras podemos observar las diferencias entre el tiempo de ejecución y el tipo de query de cada una de las consultas.&lt;/p&gt;

&lt;p&gt;La consulta sobre la columna &lt;em&gt;integer_a&lt;/em&gt; utiliza un &lt;em&gt;Parallel Seq Scan&lt;/em&gt; y se demora en ejecutarse alrededor de 39.399 ms.&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%2F68e8dikzgia94wgstlpc.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%2F68e8dikzgia94wgstlpc.png" alt="consulta utilizando EXPLAIN ANALYZE"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;La consulta sobre la columna &lt;em&gt;integer_b&lt;/em&gt; utiliza un &lt;em&gt;Index Scan&lt;/em&gt; y se demora en ejecutarse alrededor de 0.054 ms, algo así como 730 veces más rápido. &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%2Fmx846lfio5wyyay2lrrq.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%2Fmx846lfio5wyyay2lrrq.png" alt="consulta utilizando EXPLAIN ANALYZE"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;
  
  
  Índice B-Tree en columnas tipo fecha.
&lt;/h4&gt;

&lt;p&gt;Igual que el ejemplo anterior, tenemos una tabla llamada &lt;em&gt;date_tables&lt;/em&gt; con dos columnas (&lt;em&gt;date_a&lt;/em&gt;, &lt;em&gt;date_b&lt;/em&gt;), donde la última es indexada mediante &lt;em&gt;B-tree&lt;/em&gt;. De nuevo, en las siguientes figuras podemos observar las diferencias entre el tiempo de ejecución y el tipo de query de cada una de las consultas.&lt;/p&gt;

&lt;p&gt;La consulta sobre la columna &lt;em&gt;date_a&lt;/em&gt; utiliza un &lt;em&gt;Parallel Seq Scan&lt;/em&gt; y tarda en ejecutarse alrededor de 43.293 ms.&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%2Fqzmzytt0ccegjx1kcxiw.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%2Fqzmzytt0ccegjx1kcxiw.png" alt="consulta utilizando EXPLAIN ANALYZE"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;La consulta sobre la columna &lt;em&gt;date_b&lt;/em&gt; utiliza un &lt;em&gt;Index Scan&lt;/em&gt; y tarda en ejecutarse alrededor de 0.054 ms, algo así como 800 veces más rápido. &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%2F8con02cbu4u6wf6x88c1.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%2F8con02cbu4u6wf6x88c1.png" alt="consulta utilizando EXPLAIN ANALYZE"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;
  
  
  Índice B-Tree en columnas tipo strings.
&lt;/h4&gt;

&lt;p&gt;Para analizar este escenario contamos con una tabla llamada &lt;em&gt;string_tables&lt;/em&gt; que tiene dos columnas (&lt;em&gt;string_a&lt;/em&gt;, &lt;em&gt;string_b&lt;/em&gt;), donde la última es indexada mediante &lt;em&gt;B-tree&lt;/em&gt;. De nuevo, en las siguientes figuras podemos observar las diferencias entre el tiempo de ejecución y el tipo de query de cada una de las consultas.&lt;/p&gt;

&lt;p&gt;La consulta sobre la columna &lt;em&gt;string_a&lt;/em&gt; utiliza un &lt;em&gt;Parallel Seq Scan&lt;/em&gt; y tarda en ejecutarse alrededor de 52.202 ms.&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%2F0sqm1r75i859hjjp19nf.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%2F0sqm1r75i859hjjp19nf.png" alt="consulta utilizando EXPLAIN ANALYZE"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;La consulta sobre la columna &lt;em&gt;string_b&lt;/em&gt; utiliza un &lt;em&gt;Index Scan&lt;/em&gt; y tarda en ejecutarse alrededor de 0.062 ms, algo así como 841 veces más rápido. &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%2Fpcyqkuunt8s97rfanc0e.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%2Fpcyqkuunt8s97rfanc0e.png" alt="consulta utilizando EXPLAIN ANALYZE"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;
  
  
  Índice B-tree y el operador &lt;em&gt;LIKE&lt;/em&gt; e &lt;em&gt;ILIKE&lt;/em&gt;
&lt;/h4&gt;

&lt;p&gt;Ahora bien, las consultas en columnas que tienen caracteres son algo especiales debido a que podemos utilizar en ellas operadores de patrones de coincidencia como &lt;em&gt;LIKE&lt;/em&gt; o &lt;em&gt;ILIKE&lt;/em&gt;, sin embargo se debe tener especial cuidado al utilizarlos. Para entender esto, haremos algunos ejemplos que nos mostrarán cuando la base de datos utiliza o no un &lt;em&gt;Index Scan&lt;/em&gt; aun cuando la consulta se realiza sobre la columna indexada &lt;em&gt;string_b&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Caso &lt;em&gt;LIKE&lt;/em&gt;:&lt;/strong&gt; PostgreSQL utilizará el índice &lt;em&gt;B-tree&lt;/em&gt; (&lt;em&gt;Index Scan&lt;/em&gt;) cuando una wildcard como ‘%’ &lt;strong&gt;NO&lt;/strong&gt; está definida en el comienzo de la cadena, veamos un ejemplo:&lt;/p&gt;

&lt;p&gt;Se realiza un &lt;em&gt;Index Scan&lt;/em&gt; cuando la wildcard ‘%’ &lt;strong&gt;No&lt;/strong&gt; está al comienzo de la cadena de caracteres.&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%2Fivk9elc7w4rly0t9xps9.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%2Fivk9elc7w4rly0t9xps9.png" alt="consulta utilizando EXPLAIN ANALYZE"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;En el caso contrario, PostgreSQL &lt;strong&gt;No&lt;/strong&gt; utilizará el índice cuando una wildcard como ‘%’ está definida al comienzo de la cadena.&lt;/p&gt;

&lt;p&gt;Observemos en el siguiente ejemplo como se realiza un &lt;em&gt;Parallel Seq Scan&lt;/em&gt; cuando la wildcard ‘%’ está al comienzo de la cadena de caracteres. PostgreSQL no utiliza el índice en este tipo de query.&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%2F4o5tjpxd45mh7u3xcqmk.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%2F4o5tjpxd45mh7u3xcqmk.png" alt="consulta utilizando EXPLAIN ANALYZE"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Caso &lt;em&gt;ILIKE&lt;/em&gt;:&lt;/strong&gt; En este tipo de consultas PostgreSQL utilizará el índice solo si el patrón comienza con caracteres no alfabéticos, es decir, caracteres que no se ven afectados por la conversión de mayúsculas o minúsculas.&lt;/p&gt;

&lt;p&gt;Se realiza un &lt;em&gt;Index Scan&lt;/em&gt; con el operador &lt;em&gt;ILIKE&lt;/em&gt; cuando el patrón de búsqueda comienza con un carácter no alfabético, esto se muestra en la siguiente imagen donde el primer carácter del string en el query comienza con el número &lt;strong&gt;1&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%2Fag66gzgtwo00p31xcr5s.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%2Fag66gzgtwo00p31xcr5s.png" alt="consulta utilizando EXPLAIN ANALYZE"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;En caso contrario, PostgreSQL no utilizará el índice &lt;em&gt;B-tree&lt;/em&gt; para consultas con el operador &lt;em&gt;ILIKE&lt;/em&gt;. En la siguiente imagen vemos que se realiza un &lt;em&gt;Parallel Seq Scan&lt;/em&gt; con el operador &lt;em&gt;ILIKE&lt;/em&gt; cuando el patrón de búsqueda comienza con un carácter alfabético.&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%2Fr036i6gktccz538m8ink.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%2Fr036i6gktccz538m8ink.png" alt="consulta utilizando EXPLAIN ANALYZE"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Creación y uso de índice &lt;em&gt;GIN&lt;/em&gt; en &lt;em&gt;Ruby on Rails&lt;/em&gt;
&lt;/h3&gt;

&lt;p&gt;El índice &lt;em&gt;GIN&lt;/em&gt; puede ser utilizado en columnas de tipo arrays, jsonb, hstore y texto. Un ejemplo que muestra creación de este tipo de índice en una columna de tipo array de enteros se muestra a continuación, en donde se especifica en la migración utilizar un índice &lt;em&gt;GIN&lt;/em&gt; solo para la columna &lt;em&gt;array_b&lt;/em&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class CreateArrayTables &amp;lt; ActiveRecord::Migration[7.0]
  def change
    create_table :array_tables do |t|
      t.integer :array_a, array: true
      t.integer :array_b, array: true

      t.timestamps
    end
    add_index :array_tables, :array_b, using: 'gin'
  end
end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;En las siguiente figuras podemos observar y comparar la diferencia en el tiempo de ejecución entre una consulta que se realiza sobre una columna sin indexar y otra que se realiza sobre una columna indexada con &lt;em&gt;GIN&lt;/em&gt;. La consulta sobre la columna indexada &lt;em&gt;array_b&lt;/em&gt; es más eficiente.&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%2Fbagbirk6tgfoeeb37w8k.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%2Fbagbirk6tgfoeeb37w8k.png" alt="consulta utilizando EXPLAIN ANALYZE"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Se utiliza &lt;em&gt;Index Scan&lt;/em&gt; cuando la consulta se hace sobre la columna con índice &lt;em&gt;GIN&lt;/em&gt; (&lt;em&gt;array_b&lt;/em&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%2Fezi5gsqi4q3idgbi5y24.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%2Fezi5gsqi4q3idgbi5y24.png" alt="consulta utilizando EXPLAIN ANALYZE"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Índices multicolumna
&lt;/h4&gt;

&lt;p&gt;Un índice puede ser definido en más de una columna. PostgreSQL permite indexar hasta un máximo de 32 columnas, pero únicamente para índices tipo &lt;em&gt;B-tree&lt;/em&gt;, &lt;em&gt;Gist&lt;/em&gt;, &lt;em&gt;Gin&lt;/em&gt; y &lt;em&gt;Brin&lt;/em&gt;. Este tipo de índices es útil cuando tenemos consultas que involucran el operador &lt;strong&gt;AND&lt;/strong&gt; entre varias columnas. Un índice multicolumna se puede crear en Ruby on Rails de la siguiente manera&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class CreateMulticolumnIndexTables &amp;lt; ActiveRecord::Migration[7.0]
  def change
    create_table :multicolumn_index_tables do |t|
      t.integer :integer_a
      t.integer :integer_b

      t.timestamps
    end
    add_index :multicolumn_index_tables, %i[integer_a integer_b]
  end
end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;PostgreSQL utiliza el índice multicolumna creado cuando en una consulta intervienen las dos columnas indexadas con el operador &lt;strong&gt;AND&lt;/strong&gt;. Observemos como el resultados del Explain Analyze nos muestra el uso de Index Scan.&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%2Fhfocsyqwbz31od3aw0ig.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%2Fhfocsyqwbz31od3aw0ig.png" alt="consulta utilizando EXPLAIN ANALYZE"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;El índice multicolumna también es utilizado cuando se hace una consulta solo para la columna &lt;em&gt;integer_a&lt;/em&gt;, la cual corresponde a la columna definida más a la izquierda en la creación del índice.&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%2Fx8evyb9a4l4ixv4y2udc.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%2Fx8evyb9a4l4ixv4y2udc.png" alt="consulta utilizando EXPLAIN ANALYZE"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Sin embargo, PostgreSQL &lt;strong&gt;NO&lt;/strong&gt; utiliza el índice multicolumna para las consultadas que involucran el operador OR o solamente una columna que corresponda a la definida más a la derecha en la creación del índice, veamos los siguientes ejemplo para observar el comportamiento.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;NO&lt;/strong&gt; se realiza un Index Scan en un índice multicolumna para las consultas con el operador OR.&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%2Fm5ppggi7lwu7p2izcap1.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%2Fm5ppggi7lwu7p2izcap1.png" alt="consulta utilizando EXPLAIN ANALYZE"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;NO&lt;/strong&gt; se utiliza el  índice multicolumna cuando en la consulta solo interviene la columna definida más a la derecha en el índice. Recordemos que el orden de las columnas en la creación del indice fue &lt;code&gt;[integer_a integer_b]&lt;/code&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%2F86r7wdog94p72uaohvgz.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%2F86r7wdog94p72uaohvgz.png" alt="consulta utilizando EXPLAIN ANALYZE"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Combinando multiples índices
&lt;/h4&gt;

&lt;p&gt;PostgreSQL tiene la capacidad de solucionar el inconveniente anterior combinando múltiples índices definidos individualmente (incluidos múltiples usos del mismo índice). De esta forma podemos ejecutar consultas con múltiples condiciones &lt;strong&gt;AND&lt;/strong&gt; y &lt;strong&gt;OR&lt;/strong&gt; a través del uso de los índices individuales de cada columna. &lt;/p&gt;

&lt;p&gt;Para ejemplificar este comportamiento, crearemos índices individualmente para cada columna en una migración de la siguiente manera.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class CreateCombiningIndexTables &amp;lt; ActiveRecord::Migration[7.0]
  def change
    create_table :combining_index_tables do |t|
      t.integer :integer_a
      t.integer :integer_b

      t.timestamps
    end
    add_index :combining_index_tables, :integer_a
    add_index :combining_index_tables, :integer_b
  end
end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Luego de correr la migración anterior, podemos realizar una consulta con varias columnas indexadas individualmente, adicionalmente podemos utilizar el operador &lt;strong&gt;OR&lt;/strong&gt; y PostgreSQL utilizará los índices creados en la consulta.&lt;/p&gt;

&lt;p&gt;Como se muestra a continuación, en la consulta se utilizan los índices individuales de cada columna, a esto se le llama combinar múltiples índices.&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%2Fvmxv8fjd72rj317vq246.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%2Fvmxv8fjd72rj317vq246.png" alt="consulta utilizando EXPLAIN ANALYZE"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  One more thing! Cómo agregar índices a una tabla de una base de datos en producción?
&lt;/h4&gt;

&lt;p&gt;Como se mencionó anteriormente,  PostgreSQL y muchos otros RDBMS bloquean el acceso de escritura en la tabla mientras un índice es creado. Por supuesto, esto es inaceptable cuando nuestro proyecto está en producción y no podemos permitir un tiempo de inactividad considerable mientras se crea el índice. Hay que tener en cuenta que mientras más grande sea la tabla a indexar, más tiempo se tardara nuestro índice en ser creado.&lt;br&gt;
Para sortear este problema, PostgreSQL nos ofrece una herramienta que construirá el índice sin tomar ningún bloqueo que impida las inserciones, actualizaciones o eliminaciones simultáneas en la tabla. Para esto se debe pasar en la creación del índice el siguiente parámetro &lt;code&gt;algorithm: :concurrently&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;El parámetro &lt;code&gt;algorithm:  :concurrently&lt;/code&gt; permite crear el índice sin bloquear la tabla mientras este es creado. Note que se debe agregar disable_ddl_transaction! para permitir que la migración no se ejecute por defecto dentro de una transacción.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class AddIndexToSimpleTable &amp;lt; ActiveRecord::Migration[7.0]
  disable_ddl_transaction!

  def change
    add_index :simple_tables, :integer_a, algorithm: :concurrently
  end
end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Solución al problema planteado al principio del blog:
&lt;/h4&gt;

&lt;p&gt;Se decide crear un índice multicolumna &lt;em&gt;B-Tree&lt;/em&gt; en la tabla, esta migración tarda alrededor de 26 segundos en ser completada en el entorno de desarrollo local. La misma consulta realizada luego de la creación de los índices tardó alrededor de 0.15 ms en ser ejecutada, mucho más veloz que los 7.8 segundos que se tardaba antes de crear el índice.&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%2Fgwb3bvetopec5nw9pykz.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%2Fgwb3bvetopec5nw9pykz.png" alt="consulta utilizando EXPLAIN ANALYZE"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Algunos consejos finales:
&lt;/h4&gt;

&lt;p&gt;Es complejo formular un procedimiento general para determinar qué índices crear, pero los siguientes son algunos consejos que le permitirán tomar una mejor decisión.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Siempre analiza las consultas con el comando &lt;em&gt;EXPLAIN ANALYZE&lt;/em&gt;, de esta manera puedes comprender que hacer correctamente sin adivinar a ciegas qué sucederá si se agrega o elimina un índice.&lt;/li&gt;
&lt;li&gt;El índice predeterminado de PostgreSQL es el índice &lt;em&gt;B-Tree&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Utilice datos reales para la experimentación cuando va a crear y probar un índice. Es decir, que la tabla en el entorno local donde realizará las pruebas del comportamiento del índice tenga un numero de registros similar al entorno de producción.&lt;/li&gt;
&lt;li&gt;Es recomendable crear índices solo sobre las consultas ejecutadas con más frecuencia  y que son más costosas a nivel de performance. Evite crear un índice para satisfacer una consulta específica que no impacte el rendimiento de su aplicación.&lt;/li&gt;
&lt;li&gt;Tómese el tiempo necesario para abordar la solución, itere si es necesario. Es mejor analizar cuál índice es el apropiado para la consulta y descartar los índices innecesarios.&lt;/li&gt;
&lt;li&gt;No hay un límite específico en el número de índices en una tabla; sin embargo, se recomienda crear la menor cantidad de índices que satisfagan su carga de trabajo.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>rails</category>
      <category>postgres</category>
      <category>sql</category>
      <category>database</category>
    </item>
  </channel>
</rss>
