Página 3 de 82

import this: El zen de Python

A finales de los ochenta, un programador del Centro para las Matemáticas y la Informática de los Países Bajos (CWI, Centrum Wiskunde & Informatica), creó este nuevo lenguaje interpretado de programamación llamado Python (sí, es un homenaje a los Monty Python del que él es un fiel admirador). Debía ser sencillo, legible…

Aunque no es el único autor, Guido van Rossum (el programador en cuestión), se tomó muy en serio «vigilar» la filosofía que se había marcado al principio del proyecto, lo que le ha valido el sobrenombre de «Benevolente Dictador Vitalicio» en inglés, «Benevolent Dictator for Life» o, para abreviar, «BDFL». La versión 1.0 de Python vio la luz en enero de 1994.

Algunos años más tarde, Tim Peters, uno de los entusiastas programadores de Python resumió de forma concisa, casi en forma de poema, los principios de diseño en los que se basa Python y por los que cualquier programador en este lenguaje debe guiarse. De los veinte principios, solamente diecinueve han quedado escritos. Una traducción libre de estos principios podría ser algo así:

Hermoso es mejor que feo.
Explícito mejor que implícito.
Simple, mejor que complejo y
Complejo mejor que complicado.
Llano mejor que anidado.
Disperso es mejor que denso.
La legibilidad importa.
Los casos especiales no lo son tanto como para romper las reglas.
Aunque lo pragmático gana a la pureza.
Los errores no deberían pasar desapercibidos
A menos que se silencien explícitamente.
Frente a la ambigüedad, rechaza la tentación de adivinar.
Debería haber una —y preferiblemente sólo una— forma obvia de hacerlo,
Aunque esa forma no sea tan obvia a primera vista a menos que seas holandés.
Ahora es mejor que nunca,
Aunque nunca es, a menudo, mejor que inmediatamente.
Si la implementación es difícil de explicar, es una mala idea.
Si la implementación es fácil de explicar, puede que sea una buena idea.
Los espacios de nombres son una excelente idea, ¡tengamos más como ésa!

Desde el punto de vista de la programación estos diecinueve principios marcan la diferencia cuando escribes código. Tal vez, sólo tal vez, sean una buena filosofía llevada a otros campos de nuestra vida ¿no creéis?

P.D. Desde la versión 2.1.2, Python incluye estos puntos (en su versión original en inglés) como un huevo de pascua que se muestra al ejecutar la orden import this.

Referencia original en la web oficial de Python: https://www.python.org/dev/peps/pep-0020/

MongoDB covered queries

Si buscamos en la documentación de MongoDB1 una consulta cubierta o «covered query» es la que está cubierta por un índice que cumple dos condiciones:

  • todos los campos de la consulta forman parte del índice
  • todos los campos devueltos por la consulta forman parte de la propia consulta

Es decir, tanto las condiciones para filtrar los resultados como la proyección de campos deben formar parte del índice.

MongDB Coveres Queries

Este tipo de consultas consigue una mejora bastante importante ya que elimina la necesidad de examinar ningún documento de la colección que estamos consultando ya que todos los datos se encuentran en el índice. Como además los índices están cargados normalmente en RAM y son mucho más pequeños que las colecciones, la velocidad de acceso puede ser inmensamente menor.

Veamos un ejemplo. Para ello vamos a crear una colección de documentos que nos sirva de punto de partida. Esta colección contendrá 2.000 documentos que vamos a crear con el siguiente script que podemos ejecutar directamente desde el shell de MongoDB.

> for(var i = 1; i <= 2000; i++) {
... db.cosas.insert({
... nombre: 'Cosa ' + i,
... numero: i,
... campo: 'probando ' + i,
... centenas: Math.floor(i/100),
... decenas: Math.floor((i%100)/10),
... unidades: i%10
... });
... }

Con esto ya tenemos una colección para realizar nuestras pruebas. Para ver el efecto que produce en los tiempos de respuesta usaremos la consulta primero sin índice y luego generaremos el índice y repetiremos la consulta para poder comparar.

Nuestra consulta de test será:

db.cosas.find({ decenas: 3, unidades: { $gt: 3 } }, { numero: 1, _id: 0 });

Para ver qué pasa con la consulta utilizaremos un objeto explain() para que nos muestre la estadística de ejecución:

db.cosas.explain('executionStats').find({ decenas: 3, unidades: { $gt: 3 } }, { numero: 1, _id: 0 });

El resultado de la consulta muestra las siguientes estadísticas:

{
  "queryPlanner" : {
    ...
  },
  "executionStats" : {
    "nReturned" : 120,
    "executionTimeMillis" : 16,
    "totalKeysExamined" : 0,
    "totalDocsExamined" : 2000,
    "executionStages" : {
      ...
    }
  },
  "serverInfo" : {
    ...
  },
  "ok" : 1
}

Podemos ver que para resolver la consulta se han examinado 2000 documentos en 16 milisegundos para devolver un total de 120 resultados.

Si ahora creamos un índice con los campos decenas, unidades y numero los resultados varían:

> db.cosas.ensureIndex({ decenas: 1, unidades: 1, numero: 1});

{
  "createdCollectionAutomatically" : false,
  "numIndexesBefore" : 1,
  "numIndexesAfter" : 2,
  "ok" : 1
}

> db.cosas.getIndexes();
[
  {
    "v" : 1,
    "key" : {
      "_id" : 1
    },
    "name" : "_id_",
    "ns" : "coveredqueries.cosas"
  },
  {
    "v" : 1,
    "key" : {
      "decenas" : 1,
      "unidades" : 1,
      "numero" : 1
    },
    "name" : "decenas_1_unidades_1_numero_1",
    "ns" : "coveredqueries.cosas"
  }
]

> db.cosas.explain('executionStats').find({ decenas: 3, unidades: { $gt: 3 } }, { numero: 1, _id: 0 });

{
  "queryPlanner" : {
    ...
  },
  "executionStats" : {
    "executionSuccess" : true,
    "nReturned" : 120,
    "executionTimeMillis" : 0,
    "totalKeysExamined" : 120,
    "totalDocsExamined" : 0,
    "executionStages" : {
      ...
    }
  },
  "serverInfo" : {
    ...
  },
  "ok" : 1
}

En este caso, para devolver los mismos 120 resultados se han examinado 0 documentos y el tiempo de respuesta es de 0 milisegundos.

Si simplemente no excluyera el identificador de documento, es decir, en la proyección no se incluyen solamente campos del índice, esta optimización de tiempo no sería efectiva (aunque ganaríamos tiempo al usar el índice). Veamos los datos:

> db.cosas.explain('executionStats').find({ decenas: 3, unidades: { $gt: 3 } }, { numero: 1 });

{
  "queryPlanner" : {
    ...
  },
  "executionStats" : {
    "executionSuccess" : true,
    "nReturned" : 120,
    "executionTimeMillis" : 7,
    "totalKeysExamined" : 120,
    "totalDocsExamined" : 120,
    "executionStages" : {
      ...
    }
  },
  "serverInfo" : {
    ...
  },
  "ok" : 1
}

Vemos que en este caso se produce la optimización de tiempo al usar el índice creado ya que sólo se examinan 120 documentos.

La mejora de tiempo puede ser sustancial en colecciones con muchos documentos, especialmente si son documentos grandes.

Limitaciones y restricciones

Si consultamos de nuevo la documentación1 vemos que tenemos algunas restricciones en los campos indexados:

  • si algún campo indexado es una matriz, los índices multi-key no pueden cubrir una consulta
  • algún campo de la consulta o de la proyección pertenece a un subdocumento

También existe una limitación en las «sharded collections».



  1. https://docs.mongodb.org/manual/core/query-optimization/#read-operations-covered-query 
A %d blogueros les gusta esto: