seeding ghost’s DB

building ghost-powered website’s contents in a scalable way

1. what we’re doing

We’re building a custom website for a particular client, and we’ll be using ghost as our cms. This allows us to quickly bootstrap the website, building a theme based on a custom design our client has previously picked.

2. the problem

To put this in a clear way, there’s three parts of the website we’ll deliver to our client:

  1. ghost, the CMS that will run our site
  2. a custom theme for our client, which has their colors, fonts, etc.
  3. the contents for the theme itself. the theme must have some contents in order to be fully displayed

It’s on the third item in the list where our problem lies on. Maybe it has not been paid too much attention, but there has to be a scalable way of handling the “base” or the “default” data the theme features. Some way in which a developer doesn’t see different contents than his partner when building the UI or when structuring the components and the elements for the webpage.

And it goes further than that – we’ll have:

  • one development environment per development machine (and there’ll be a couple of persons building it simultaneously)
  • a staging environment (or several of them, depending on how big our team) in which will be deployed automatically off from master branch and checked constantly so that we QA this shared env
  • a production environment – which won’t be created right away but needs to be easily deployed (and re-built) with the “initial” data once we are ready to deliver

It’s important for our building team to have a similar copy of each one of these. Keeping everybody in the same page will avoid us misunderstandings.

3. why not use DB dumps?

Well, there’s a few reasons. First, each environment must be using a different DB engine. For convenience, development environments will be using sqlite3 which is better for a bootstrap, while production environments will be running PostgreSQL.

4. the solution

The missing piece must be a program which can create (and re-create) in a one-off-run the “initial” data for the database (aka seed the database) in order to allow us to show correctly our theme

5. how to?

We’ve released a gem, which will read fixtures (yml files) for the models that currently make up ghost’s structure and will create the necessary DB records. This way our contents are written in simple text files rather than hardcoded on the theme itself.

This project has some seed fixtures we can take a look as a sample: github.com/prendho/webpage/tree/master/config/seed/fixtures

And this is the ruby gem, I hope we’ll be documenting in the close future: rubygems.org/gems/ghost-seeder

I’ll be writing some more documentation in our GH repo github.com/noggalito/ghost-seeder but basically at the moment we run the task using rake and it creates (or re-creates) the necessary data to make up the website.

$ rake db:seed

ghost-seeder db:seed task

ghost-seeder db:seed task

TL;DR

We add a ruby gem to our projects, which allows us to run a simple task and based on some “initial” data we deploy the entire website with it’s contents in a few seconds.

Gema para validar cédula o RUC de Ecuador

Hice una gema para Ruby (compatible con los modelos de ActiveRecord que utiliza Rails) que permite validar la cédula o RUC de Ecuador.

Su uso en este enlace

Enlaces:

Hace algún tiempo también hice la misma implementación, pero en JavaScript y con un plugin para jQuery. Su enlace en github:

https://github.com/macool/cedula-ruc-ecuador-validator-jquery 

 

MySQL vs PostgreSQL vs MongoDB (velocidad)

Bueno, después de algún tiempo de escuchar comentarios como “MySQL no te sirve para un sitio pequeño, PostgreSQL es mucho mejor”, o “MongoDB no es bueno”, me decidí por probar por mi mismo la velocidad de cada uno de estos motores.

Preparando la DB

Empecé colocando en cada uno, una base de datos real de tuits, que he estado recolectando desde hace unos dos años con un bot (@cuxibamba; utiliza MySQL, y, de momento, tiene 69255 tuits y 12313 usuarios)

Para colocar la base de datos de @cuxibamba en MongoDB, utilicé MongoHub, que tiene una utilidad nativa para importar de MySQL.

Tuve un poco de problemas colocando la data dentro de PostgreSQL, pero al final lo logré con taps (muy recomendado, por cierto).

Capturas de cómo inician las DB:

PostgreSQL

PostgreSQL

MySQL

MySQL

MongoDB

MongoDB

Primer test

Contando los tuits 10000 (diez mil) veces (count(*))

PostgreSQL count

PostgreSQL count

MySQL count

MySQL count

MongoDB count

MongoDB count

Resultados:

  • PostgreSQL: 164.97 segundos
  • MySQL: 151.67 segundos
  • MongoDB: 4.69 segundos (35 veces menos que PostgreSQL)

Pd. Sí, yo también me sorprendí, y revisé dos veces el script, por si me había equivocado en algo.

Segundo test

Consultando una palabra aleatoria (generada por ruby) 500 veces (where text like '%word%'). Dos partes:

  1. contar cuántas veces aparece dentro del contenido de los tuits
  2. hacer la proyección de los resultados; no solamente el conteo
PostgreSQL query random word

PostgreSQL query random word

MySQL query random word

MySQL query random word

MongoDB query random word

MongoDB query random word

Resultados:

  • PostgreSQL: 20.71 segundos al contar cuántos resultados (count()), 20.77 segundos al hacer la proyección de los resultados y asignar a una variable cada uno.
  • MySQL: 26.4 segundos al contar cuántos resultados (count()); 69.91 segundos al hacer la proyección y asignar los resultados a variables.
  • MongoDB: 63.7 segundos al contar los resultados (count()); 65.08 segundos al hacer la proyección y asignar los resultados.

Conclusiones de este test:

  1. PostgreSQL se toma el mismo tiempo en hacer un conteo de los datos que coinciden con los criterios, que cuando selecciona los datos. Logró un muy buen tiempo en este test.
  2. MySQL se toma mucho menos tiempo cuando cuenta cuántas tuplas coinciden con el criterio, que cuando selecciona cada tupla para utilizarla luego
  3. MongoDB se tomó mucho más tiempo en este test. Probablemente esto sea culpa mía; no pude encontrar un equivalente a: select * from tweets where text like '%word%'; en mongo; en su lugar, estoy utilizando una expresión regular, la cual, obviamente va a coincidir con más resultados y va a costar más recursos.

Tercer test

Select buscando por id (primary key) 1000000 (un millón) veces (los ids son generados aleatoriamente por ruby entre 1 y 69255).

PostgreSQL find by id

PostgreSQL find by id

MySQL find by id

MySQL find by id

MongoDB find by id

MongoDB find by id

Resultados:

  • PostgreSQL: 763.98 segundos
  • MySQL: 652.39 segundos
  • MongoDB: 552.51 segundos

Cuarto test

En mi opinión, el más importante: el CRUD, compuesto por lo siguiente:

  1. Crear 10000 (diez mil) tuits.
  2. Encontrarlos (por su id único) y actualizar su texto (guardándolo en DB nuevamente).
  3. Encontrar nuevamente los 10000 tuits y actualizar tres veces el contenido de cada uno, guardando en DB cada vez que se actualice. (Es decir, tres veces por tuit)
  4. Encontrar cada uno de los 10000 tuits (por su id) y eliminarlo 
PostgreSQL CRUD

PostgreSQL CRUD

MySQL CRUD

MySQL CRUD

MongoDB CRUD

MongoDB CRUD

Resultados:

  • PostgreSQL:
    • 38.82 segundos para almacenar diez mil tuits
    • 35.45 segundos para encontrar y actualizar diez mil tuits
    • 106.39 segundos para encontrar los tuits y actualizar tres veces cada uno
    • 23.54 segundos para eliminar los tuits
    • Total: 204.2 segundos
  • MySQL:
    • 40.18 segundos para almacenar diez mil tuits
    • 29.71 segundos para encontrar y actualizar diez mil tuits
    • 87.32 segundos para encontrar los tuits y actualizar tres veces cada uno
    • 22.55 segundos para eliminar los diez mil tuits
    • Total: 179.76 segundos
  • MongoDB:
    • 4.81 segundos para almacenar diez mil tuits
    • 18.13 segundos para encontrar y actualizar diez mil tuits
    • 54.86 segundos para encontrar los tuits y actualizar tres veces cada uno
    • 14.9 segundos para eliminar diez mil tuits
    • Total: 92.7 segundos (casi la mitad de MySQL, menos de la mitad de PostgreSQL)

Conclusiones: (?)

  • MongoDB rocks, NoSQL rocks (sí, soy fanboy)
  • MongoDB podría llegar a ser el doble de rápido comparado con MySQL y PostgreSQL (?)
  • En el segundo test, como detallé, MongoDB se queda atrás por bastante porque no he podido encontrar (todavía) una manera de hacer where field like %text% (probablemente debe haberla); y tuve que utilizar una expresión regular que disparó el consumo de recursos
  • MySQL es más rápido que PostgreSQL, excepto cuando tiene que hacer una proyección con una (o varias) condiciones (where). Será que PostgreSQL indexa mejor los datos? (ver segundo test)
  • A PostgreSQL le costó bastante actualizar la información de cada tuit (último test)

Espero que les sirva de algo. El hardware de mi Mac es:

  • Procesador: 2.3 Ghz Intel Core i7
  • Memoria: 8GB 1600Mhz DDR3
  • HD: APPLE SSD SM256E Media ( ~500MB/s )