Handling batch operations with REST APIs

So, you created your REST API following the best practices, named your endpoints accordingly, used the correct HTTP verbs and everything is working well.

For example, you create users by making a POST /users call, get a list of them using GET /users, or get a single one by doing GET /users/:userId


Developers are happy, customers using the API are happy, what a beautiful world!

Until, someone comes and says: “Hey, I need to import 10000 users”. Ouch.

Your first thought might be, “well… just do a for loop and make a POST for each one of them, I don’t care if it takes half an hour”

That may be a solution in most of the cases because as I said, you built your API following the best practices and your underlying cloud infrastructure is horizontally scalable, you can create as many instances as your credit card allows, with almost unlimited computing resources like CPU or RAM.
But there is something that doesn’t scale too much, networking.

Why networking is an issue?

Networking or the number of calls you need to make is a bottleneck, each networking call needs to negotiate a complicated protocol like TCP and find its way through an unreliable global network of routers and switches.

Some clients may face the issue of having a hard limit of outbound connections. (As I did, for an API I built myself, which triggered this post)
Usually, an outbound connection from within a system uses SNAT which means one TCP port needs to be used per each request. Poorly built or complex systems may have a really low amount of available (allocatable) TCP ports.

How can we fix it?

The solution for this networking issue is to have some way of sending multiple items in a single call. Therefore, we can make fewer requests with more data as opposed to making a single request per user.

But this means we need to do changes in our beautifully designed API. Not only that but it also means you need to start asking yourself a lot of new questions, for example, what happens if I (as an API) receive an array of users, and when processing one of them fails due to the lack of a required property. What should be the response code? do I return 200? 400? do I return an array of response codes?

That is totally the opposite of best-practices. So we need to find a better way.

What options do we have?

We have several options to fix this issue, let’s enumerate them:

  • Change your contract to accept arrays in the body
  • Change your server-side code to accept multiple body formats
  • Rename your endpoints
  • Create a new endpoint for arrays
  • Create an endpoint for receiving batches (for each entity)
  • Create a new batch endpoint

Now, let’s take a deeper dive into each one of the options, and let’s talk about why you should use it or not:

Change your contract to accept arrays in the body

This might be the first thing that came to your mind, hey, let’s just accept an array instead of an object as we do nowadays.

So instead of:

POST /users
  "username": "Diego",
  "password": "123456"

You would do:

POST /users
    "username": "Diego",
    "password": "123456"

That sounds nice, but it is an anti-pattern and worst, if you have your API already published with customers using it, it means it is a breaking change. It is not backward-compatible, it is a contract change.

You could still use this approach if you change the version, like POST /v2/users

Change your server-side code to accept multiple body formats

If you created your API using… let’s say node, express, and swagger/openAPI, you might be tempted to fork the swagger library and modify its code.

This is the worst option of all (I believe). It means you need to change the libraries you used to create the API, and make them more “smart” so they don’t explode and route the traffic differently if an array was received instead of an object.

This is, again, an anti-pattern. DON’T DO THIS PLEASE.

Rename your endpoints

What if instead of users, we change its name to user and then make the users endpoint to accept array and user to accept a single object…

This is another anti-pattern, entity names should be in plural. (not to mention that this is also a breaking change)

Create a new endpoint for arrays

Ok, now things are taking more shape… what if we do POST /usersArray

It is a new endpoint, at first it is not an anti-pattern, but looks fishy. I wouldn’t recommend this approach since it can become inconsistent quickly. Although it may be a good “quick-fix” it is not a long-term solution.

Create an endpoint for receiving batches (for each entity)

We can do POST /users/batch

Hey, this kind of looks nice. You are exposing a new endpoint for the POST method, you will need to add logic in your controller to do a batch job with the received array, but it is not an anti-pattern and is one of the recommended ways to go.

Create a new batch endpoint

This takes the previous approach to the next level, providing a good long-term re-usable solution.

Instead of POST /users/batch you can do POST /batch/users

This sight order change means a huge difference in the backend. If your API is using microservices, this means a completely new batch microservice. If not, it means a completely new controller, like the users one, but this one is called batch.

The purpose of this controller is to run batch jobs against the API endpoints.

So we can do:

POST /batch/users
    "method": "POST",
    "body": {
              "username": "Diego",
              "password": "123456"
    "method": "POST",
    "body": {
              "username": "Diego2"

And the response code of the batch endpoint would almost always be 200, but the response body can contain an array with each single response code:

    "responseCode": "200",
    "responseBody": {
              "userId": "12839"
              "username": "Diego"
    "responseCode": "400",
    "responseBody": {
              "error": "Missing password"

You can take this even further, by allowing async jobs by adding an ID to each batch job, then you can query for that ID to get the results.


From all the options, only three of them are not anti-patterns, you can change your contract and accept arrays in a newer version of your API if you are willing to confront the risks of having an inconsistent experience, or you can use any of the last two options to build a batch endpoint.

I tend to choose the last one (build a batch microservice/controller) as the “best” option, but it really depends on your API, the business context, and some other factors.

Service Mesh comparison – Istio vs LinkerD

What is a service mesh?

A service mesh is in charge of managing the network traffic between the services. It does it in a more automated and scalable way compared to otherwise what it would take a lot of manual work. (and we know that manual work is not good 🙂 )

In other words, the service mesh lays in top of Kubernetes, takes over your services networking and makes the communications safe and reliable. It allows to connect, secure and monitor your services.

You can focus on writing your microservices and leverage the observability, network, and security policies to the service mesh.

Key capabilities of a service mesh

We can split the key capabilities in three:

  • Traffic Management
    • mTLS
    • Fault Injection
    • Circuit Breaking
    • Deployment Strategies
  • Metrics/Observability
    • Out-of-the-box monitoring and tracing tools
  • Security/Policies
    • Enforce policies and isolation

A service mesh enables “intelligent route control” capabilities, along with end-to-end encryption in the communications between the services. In addition, it also enables fault injection and advanced deployment strategies like blue/green, canaries, rolling upgrades, and more.

A service mesh becomes the “dashboard of your microservices“, a place where you can view and control what’s going on inside your cluster.

Service mesh comparison

When it comes to service mesh for Kubernetes, there are two big players, Istio and LinkerD. Both have amazing features and work in a very similar way so it is often a complicated choice.


  • Istio is a Kubernetes native service mesh, but it supports other orchestration tools like Consul and even VMs.
  • It was created by Lyft, but Google and IBM are now supporting it. Today Anthos comes with “Istio” service mesh capabilities.
  • It relies on a control plane and sidecars for working
  • We can say it is the most popular Kubernetes Service Mesh
  • It is very customizable
  • It is complex


  • Mirrors Istio architecture closely. (sidecars, control plane)
  • It is part of the Cloud Native Foundation (CNCF)
  • LinkerD focuses on simplicity instead of Flexibility
  • It is a Kubernetes-only solution
  • We can say it is the second most used Kubernetes service mesh

Comparison Table

Here you can find a summary of a comparison between the features:

Mesh Features
Encrypted trafficYes. Auto mTLSYes, but not for TCP. (mongo connection)
Inter microservices traffic managementYes. PoliciesLimited
Microservices metrics for automatic scalabilityYes. PrometheusYes. Prometheus
Real-time mesh status observabilityYes. Kiali. (more graphical)Yes. (own dashboard, simpler)
Microservices Rate LimitingYesNo
Microservices Testing Features (fail injection, delays, % balancer)YesLimited
Microservices Circuit breakingYesNo
Microservices Release mechanisms (canary, green/blue, etc.)YesYes
Monitoring integration and metrics standards (opentelemetry)Yes. Grafana, Jaeger (tracing)Yes
Access Logs GenerationYesNo out-of-the-box (possible with plugins)
Does it work for Inter-cluster communications? (multi-cluster)YesNo
Mesh Generic stuff
Easy of UseComplexMedium
Companies behind itGoogle, Lyft, IBMBuoyant
Market acceptance/maturity. Community sizeLargeMedium
Support ModelCommunity and Google on AnthosCommunity. Buoyant
License/CostApache 2.0Apache 2.0
ProsMany Features. Can be extended.Easy to Use
ConsComplexDeeply Integrated with kubernetes. Cannot be expanded.

Comparison Conclusion

In conclusion, Istio is very flexible, but it is also very complex, therefore the learning curve is really big, yet it allows you to do more. LinkerD on the other hand focuses on simplicity, so it is easy to use but less customizable.

Microservices contract and versioning

What is it? Why is it important?

This is a series of posts I’m doing about designing microservices. Keep posted I will link them up when they are all ready!

When you are designing a microservices architecture, whether you are using a REST or messaging approach for communication between microservices, you have to design the APIs/messages and how a microservice will interact with each other.

One of the most important aspects of the microservices architectures is the ability to work on and deploy a microservice, completely independent of each other. To achieve this, each microservice must provide a well-defined, versioned contract.

The microservices contract

A microservice contract is between the service and its clients. The main goal is that you can make changes to the service without affecting the clients, nevertheless if the clients are aware of the service changes or not.

Even if you spent a lot of time the first time, designing the initial contract, for certain, the API will need to change over time.

When the time comes to update an API, it is important to understand the difference between breaking and non-breaking changes, when a major release is required and when to dispose of an old version.

When the changes are small, for example, adding a new parameter to the API, if that parameter isn’t business-critical, the clients should be able to consume the API in the old way, without sending or expecting to receive that parameter, and the server should fill the blanks with default values.

However, if you are doing a major, backward-incompatible, change to the API, you will need to maintain the old version for some time because you as a service cannot force your clients to update immediately.
If you are using a REST approach, one way is to add a versioning number in the path, for example /app/v1/service, app/v2/service. This way you can have two or more versions of your microservice available.

That is key to understand, if you are not doing a breaking change, there is no need for a new version of your contract.

Running php-fpm and nginx processes in the same container with supervisord

Yes, I know…. containers are not meant to be used like this, running two processes within a single container. Ideally, one container runs only one process.

But, what if we want to have php-fpm + nginx on the same container? since we do need both processes running for serving our website, we may say that there is no benefit in having them in two separate containers, and in case one fails, the other is useless and the whole container should be restarted.

So, we decide to have them both in the same container. How to do it properly?? with supervisord


It is a process management daemon that will allow us to monitor and control processes on Linux.
It is quite extensive but we are not going to use all of it. we just need to run two freaking processes.

How to do it?


FROM debian:stretch

# make sure you install supervisord
RUN apt-get -qq update > /dev/null && apt-get -qq upgrade -y > /dev/null; \
    apt-get -qq install -y ... supervisor  > /dev/null;

# do your stuff, install php, nginx, whatever do you need.
# .
# .
# after you did everything, set up supervisord

COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf

CMD ["/usr/bin/supervisord"]

The changes in the Dockerfile are straight forward, just:

  • install supervisord
  • copy the config file
  • make docker CMD run supervisord



command=nginx -g "daemon off;"

command=/bin/bash -c "mkdir -p /var/run/php && php-fpm7.1 --nodaemonize --fpm-config /etc/php/7.1/fpm/php-fpm.conf"

Now things are interesting….. let’s break them up:

  • nodaemon=true we tell supervisord to run as a foreground process.
  • program:nginx we run nginx with the “daemon off” directive, we set it to auto-restart in case it fails and most importantly, we redirect logs to stdout and stderr so that docker can pick them up.
  • program:php-fpm we first create the /var/run/php folder, so php doesn’t fail to start, then we run php-fpm as a foreground process too. We do the same thing we did for nginx, redirecting the logs to stdout and stderr

And…. that is all! you now have php-fpm and nginx running on the same container, in the proper manner, with supervisord supervising them!

Resource management strategy for docker containers on Kubernetes (nodejs + express)

It seems simple, but it is not.

How to properly assign resources requirements and limits to a HTTP dockerized microservice running on kubernetes?

Good question, right?

Well, as you can imagine there is not a single answer to it. But there is a strategy you can follow. This is not a “book definition” what I mean with this is that there might be another way of sizing resource requirements, but, so far, I’ve been using this method successfully.

It is a delicate balance between the hardware size on which your containers will run (ie: k8s nodes) and how many requests the container itself can handle.

The idea is to find that delicate balance so our pods can be scheduled on the nodes, without wasting precious resources that can be used for other workloads.

So, how do we do that?

A simple method:

Let’s take a simple microservice as an example: a nodejs express REST API which talks to a MongoDB.
We will be doing: GET /example

Run the container on your local machine or a k8s cluster, measure the idle resource usage:

  • CPU: 1%
  • Memory: 80Mb

Using a load testing app like Apache JMeter, we fire up 100 concurrent requests in 1 second… measure the resource spike using docker stats or similar:

  • CPU: 140%
  • Memory: 145Mb

We exceeded one CPU core, and that is not good, so we reduce the number of parallel requests to 70

  • CPU: 93%
  • Memory: 135Mb

Now, we have something to work with. We can assume that a single instance of our app, can handle up to 70 requests per second.

Setting Kubernetes resources:

According to how resource limits and requests are set in Kubernetes, we can say that it is safe to consider that our resource requests can be:

  • CPU: 0.1
  • Memory: 100Mb

And we can limit the resources to:

  • CPU: 1
  • Memory: 150Mb

That will ensure that we can handle at least, 70 requests per second, per replica.

Disclaimer: as I said before, this is a simple approach, we are not considering several things, for example, node resources, MongoDB capacity…

Configurando un Reverse Proxy con Apache

Un Proxy es un servicio que actúa como intermediario entre una comunicación del tipo Cliente-Servidor.

Mientras que un proxy normal (Forward Proxy) hace que un servidor no te contacte directamente, sino que es el proxy quien se conecta con el servidor, manteniendo al cliente en el anonimato, un Reverse Proxy, mantiene al servidor en el anonimato para con sus clientes…


Éstas imágenes lo aclaran muy bien:

Forward Proxy:

Forward Proxy

Reverse Proxy:Reverse Proxy

(Gracias Wikipedia por las imágenes)


Para qué podemos utilizar un Reverse Proxy?

Los usos son muchos, pero el clásico es el siguiente:

Supongamos que instalamos un servicio en nuestro servidor, este servicio responde al puerto 8080, es decir, accedemos a este servicio mediante http://www.example.com:8080.

Pero queremos acceder al servicio mediante http://servicio.example.com porque es más fácil para los usuarios.

Obviamente, no podemos configurar el puerto del servicio para que use el 80 porque tenemos un Apache o que ya está corriendo en ese puerto…

Entonces, “enmascaramos” la verdadera URL con el Reverse Proxy…


Como configurar un Reverse Proxy en Apache:

Primero instalamos un paquete que nos va a servir:

apt-get-install libapache2-mod-proxy-html

Luego, activamos los módulos de apache:

a2enmod proxy 

a2enmod proxy_html

service apache2 restart

Con eso, ya estamos listos para crear el proxy.

Es muy sencillo:

NameVirtualHost *:80
<VirtualHost *:80>
	ServerName service.example.com

	ProxyPreserveHost On
	ProxyRequests Off	

	ProxyPass / http://www.example.com:8080/
	ProxyPassReverse / http://www.example.com:8080/

Esas lineas dentro de un Virtual Host de Apache, son suficientes para que el proxy funcione

Pero, si el servicio que esta detrás del proxy utiliza mucho Ajax/JavaScript/CSS, vamos a notar un rendimiento muy pobre, en otras palabras, si accedemos al sitio desde la URL original: www.example.com:8080 tendremos una respuesta normal, pero si accedemos mediante la URL enmascarada, service.example.com, podemos llegar a tener demoras de 10 a 20 segundos!! inclusive puede ocasionar Time-Outs…

Es decir, un sitio web detrás del Reverse Proxy, se va a notar muy muy lento. Mucho más lento que accediendo desde la URL original.

Esto sucede porque por defecto, el mod_proxy solo re-mapea las URLs en los headers, no en el contenido de la página, además el modulo mod_proxy_html tampoco parsea CSS o JavaScript, entonces las URLs que se encuentran dentro de esos archivos y el contenido de la página, no son re-mapeadas, provocando una serie de Lookups que demoran mucho tiempo


Entonces, el código final, con lo  anterior solucionado sería el siguiente:

NameVirtualHost *:80
<VirtualHost *:80>
	ServerName sub.example.com

	ProxyPreserveHost On
	ProxyRequests Off	

	ProxyPass /
	ProxyPassReverse /
	ProxyHTMLURLMap /

	SetOutputFilter  proxy-html
	RequestHeader    unset  Accept-Encoding



Luego, hacemos un último:

service apache2 restart

Y ya estamos listos….


Con eso deberiamos poder acceder a http://service.example.com exactamente igual que si accediéramos por la URL original!!!



MySQL: Analizando la performance de un SELECT

Me encontré con la necesidad de saber con exactitud cuánto demora un SELECT de una fila cuando el campo que buscamos no es un índice, o si es un índice secundario o uno primario.

Para explicar mejor la situación, propongo el siguiente ejemplo:

Tenemos la siguiente Tabla de Usuarios:

Id Name UserName Telephone Notes


La configuración que primero se nos ocurre es que el campo “Id” sea un índice primario, pero qué sucede si por alguna razón, tenemos que hacer siempre un SELECT donde en el WHERE se use con la columna UserName, por ejemplo, si tenemos que loguear al usuario y el dato que tenemos es el username, la consulta sería:

SELECT * FROM Users WHERE UserName=’pepe’;

En cualquier sistema, hasta en un servidor con escasos recursos, esta consulta no tarda mucho y poco importa si tarda unos milisegundos más o menos…  pero en ciertas ocasiones, con tablas de miles de registros,  dependiendo del entorno,  los milisegundos comienzan a tomar importancia, sobre todo si no tenemos mucho poder de procesamiento, el timing comienza a ser de mucha importancia.

Las bases de datos actuales, para resumirlo de una forma brutal y básica, guardan la información en estructuras de datos ordenadas (árboles avanzados) por índices, lo cual nos hace pensar que si buscamos por el campo “Id” es más rápido que si buscamos por el campo “UserName” ya que el campo “Id” está indexado.

Qué podemos hacer entonces??

Hagamos que UserName sea un índice!!!  Muy bonito, pero, cuando la base de datos busca, comparar un INTEGER (“Id”) es muchísimo más rápido que comparar un VARCHAR (“UserName”) de unos 20 caracteres de longitud, por lo tanto, cualquier operación en la tabla se vuelve un poco más lenta…

Entonces, que conviene más?  Usar un índice que haga la tabla más lenta en general, pero que devuelva una fila mucho más rápido? o usar un índice ágil, pero luego tendremos demoras al buscar una sola fila por otro campo?

La respuesta a esta última pregunta, depende un 100% del entorno del sistema en el que estemos…
En un sistema con pocos usuarios, podría no ser un inconveniente, pero cuando tenemos un servidor con escasos recursos, como por ejemplo la Raspberry Pi o similares y en nuestra aplicación una diferencia de milisegundos importa, se convierte en un inconveniente.

Se me ocurrieron algunas opciones para medir el rendimiento y realizar algunos benchmarks de MySQL corriendo con bajos recursos…

  • Comparar los resultados usando:

    • a)  El campo Id como Índice Primario y el resto campos comunes. (Situación normal)
    • b)  El campo Id como Índice Primario y el campo UserName como Índice Secundario
    • c)  El campo UserName como Índice Primario y el campo Id como Índice Secundario
  • Comparar el comportamiento de las configuraciones anteriores con distintas cantidades de registros:

    • a)  100 (cien) registros
    • b)  1000 (mil) registros
    • c)  10000 (diez mil) registros
    • d)  100000 (cien mil) registros
    • f )  1000000 (un millón) de registros
  • Ya que estamos haciendo benchmarks, comparemos los resultados anteriores con los dos motores más populares de MySQL:

    • a)  MyISAM
    • b)  InnoDB

Cómo hago estos benchmarks???

Para ello, escribí unas pocas lineas en PHP, que me permitieron llenar una BD de pruebas con información aleatoria y precisa para las pruebas…  si, en total generé más de 6.500.00 filas! casi 1GB de registros aleatorios. (demoró varios minutos)
Por si a alguien le interesa, al final del post les dejo la descarga del archivo PHP utilizado para generar los datos aleatorios, la estructura de la base de datos sin registros y la bd llena de datos.

Condiciones de las pruebas:

  • Las consultas se hacen en PhpMyAdmin.
  • Se busca un registro a la mitad del total de la tabla.
  • El tiempo es obtenido de PhpMyAdmin.
  • El servidor MySQL corre en una Raspberry Pi.
  • El sistema operativo es Raspbian ‘Wheezy’.
  • Las configuraciones de Apache/PHP/MySQL son las que vienen por defecto al instalarlos.
  • El cache MySQL es reseteado antes de cada prueba.
  • El campo UserName es un VARCHAR de 25 caracteres.
Ejemplo de consultas:
SELECT * FROM  `innodb_100_usuarios_noindex` WHERE id=50;
(1 total, Query took 0.0061 sec)
id	name	           username	                 telephone	      notes
50	7Rge46fiCLEp8Au	84cdde86a4560c10000000050	vjA0WXmydgFTLf1	84cdde86a4560c10000000050 - 50

SELECT * FROM  `innodb_100_usuarios_noindex` WHERE username='84cdde86a4560c10000000050'
(1 total, Query took 0.0073 sec)
id	name	           username	                 telephone	      notes
50	7Rge46fiCLEp8Au	84cdde86a4560c10000000050	vjA0WXmydgFTLf1	84cdde86a4560c10000000050 - 50

Basta de palabras!!!   quiero ver los números!

Todos los valores de tiempos están expresados en mS (milisegundos).

Aquí tenemos dos tablas, una con los resultados de MyISAM y otra con los de InnoDB. En ambos casos comparamos las tres configuraciones de índices mencionadas anteriormente (a, b, c) en distintas cantidades de registros. Primero buscando por el campo ID y luego por el campo UserName.


[table id=1 /]


[table id=2 /]


A primera vista, podemos notar la diferencia entre InnoDB y MyISAM en cuanto a la performance de los SELECTs.  Si tomamos como referencia las primer columna, donde ID es un Índice Primario, en InnoDB buscar un indice en 1.000.000 de registros, es un 35% más lento, pero si buscamos un registro no indexado, InnoDB es un 440% más lento que MyISAM.

Entonces, descartamos InnoDB para el resto de las comparaciones.

Evidentemente, buscar un registro que no es un índice, en un millón de filas, demora 8158.8 milisegundos, eso es es más de 8 segundos!!!  De hecho en 10.000 filas, demora casi 100 milisegundos, lo cual, en algunos casos, puede ocasionar problemas. Obviamente y como podíamos imaginar, si el tiempo es clave, ésta es la peor configuración.

Nos quedan dos opciones, utilizar el campo UserName como un indice secundario, o como un indice primario.

Como se puede ver en la tabla, si buscamos la mejor velocidad, nos conviene hacerlo un índice primario, pero en contra, la busqueda por ‘Id’ demora un par de milisegundos más.

Conclusión Final:

Como conclusión final, voy a elegir la configuración de “UserName como índice secundario“.  Porqué? Porque en promedio, parece tener los mejores tiempos, ningun tiempo supera los 6ms, sin importar si es índice primario o secundario. Realmente, muy buenos timings.

Iniciar y apagar Máquina virtual VirtualBox autoáticamente junto con el SO. (Ubuntu)

La mejor forma de iniciar una maquina virtual de VirtualBox al iniciar el sistema operativo host, y a su vez, apagarla cuando éste se apague, es con un script de init.d

Creamos un archivo para guardar el script:

sudo nano /etc/init.d/VM

Copiamos el siguiente contenido en el archivo y guardamos con F2.

#! /bin/sh
# /etc/init.d/VM

#Editar las siguientes variables

case "$1" in
    echo "Starting VirtualBox VM..."
    sudo -H -b -u $VMUSER vboxmanage startvm "$VMNAME" --type headless 
    echo "Saving state of Virtualbox VM..." 
    sudo -H -b -u $VMUSER vboxmanage controlvm "$VMNAME" savestate
    echo "Usage: /etc/init.d/VM {start|stop}" 
    exit 1 

exit 0

Nota: la acción de stop del script, no apaga la VM, sino que guarda el estado y la cierra.

Le damos permisos de ejecución al script:

sudo chmod +x /etc/init.d/VM

Ahora, le decimos al script que la máquina virtual sea lo último que se inicie, y lo primero en apagarse.

sudo update-rc.d VM defaults 99 01

PD: Encontré este script acá, pero le hice un par de modificaciones para mejorarlo.

Cumplí mi sueño. Visita a Google, en Mountain View.

  • Cumplí mi sueño. Visita a Google, en Mountain View.

Visita a Google
Visita a Google

Al momento de escribir esto, todavía me cuesta creer lo que viví hace unas semanas en Sillicon Valley, fue mucha emoción para un solo dia!

La cuna de la mayoría de los avances tecnológicos e informáticos del mundo, el lugar donde empezó todo…
donde nació Cisco, Apple, HP, Intel, Oracle, Sun, Fairchild, National, Microchip… y muchas otras más.


Mi visita fue muy corta, en unas horas, recorrí algunos edificios de Cisco, Intel y el Museo de la Historia de la Computación, todos espectaculares, sin embargo mi cabeza pensaba solo en una cosa…  Google.

Para alguien como yo, Google representa una parte importante de la vida. Desde los 12 años, cuando logré obtener una invitación para Gmail, uso los servicios de Google todos los días, todo el día! Ademas, yo personalmente, creo que Google tiene una gran responsabilidad de que internet sea hoy en día lo que es.  Te imaginas que pasaría si Google Maps no existiese? las personas seguirían usando esos mapas de las estaciones de servicio, de un metro y medio de ancho, doblados en 20 partes… o aun peor, si Gmail no existiese, estaríamos mandando emails con outlook!! como en los 90′.


Antes de ir, investigué un poco acerca de Google y sus headquarters, sabia de su política de “no visitas” pero también sabía que hay excepciones.  Así que decidí probar suerte, dije -Le voy a mandar un Email a Google, diciendo que voy desde Argentina para visitarlos y bla bla bla…- En menos de 5 minutos, obtuve una respuesta, (creo que fue un mensaje automático jaja). El mensaje decía algo como -Agradecemos tu interés de visitarnos y bla bla, pero lamentablemente no aceptamos visitas.-

Cuando estaba camino a Mountain View, pensaba que me iba a encontrar con un campus completamente cerrado, con rejas al rededor y un guardia en la puerta que no me iba a dejar entrar, pero, me lleve una sorpresa…  El campus es tan grande, hay tantos edificios, que podes recorrer los jardines entre los edificios y las calles tranquilamente. Mientras que no entres en ningún edificio, excepto la cafetería y el gift shop, está todo bien!!

Así fue como recorrí el jardín interior del Googleplex. Un lugar completamente “raro”. Con bicicletas de colores por todos lados, estatuas locas con leyendas escritas en la base, monumentos raros, un dinosaurio…   y bueno, todo tipo de cosas extrañas.  Yo sabia, había visto/leído algo acerca de eso, pero, en serio, realmente cosas muy raras jaja.
Les dejo algunas fotos:

Eso no termino ahí  Me quería sacar una foto al lado de un cartel que dijera “Google”, los hay en todas las esquinas de las calles, así que camine un poco, y cruzando la calle obtuve la foto que quería. No se porque razón  pero, camine unos metros más, hacia el frente del edificio de esa cuadra, y me lleve la mejor sorpresa del día….


Si. The Android Building.  Sinceramente, jamas se me cruzo por la cabeza, que algún día iba a poder caminar entre las estatuas de las versiones de Android.   JAMÁS. Sentí que mis pies estaban ahí, pero mi cabeza andaba flotando jaja.

Después de esperar unos 10 minutos que los chinos/japoneses/coreanos con camaras de 10.000 dolares terminaran de sacarse fotos, obtuve una de las mejores fotos de mi vida….

No soy muy fotogénico, por lo general, odio salir en las fotos, pero en ésa, realmente quería estar.

Así concluyo mi visita,

“ah”, cuando volvía al parking, vi un auto Tesla model S, creo que debe ser de Larry o Sergey, ya que el auto no estaba  a la venta todavía…  muy lindo el auto.
“ah2”, también vi una ardilla en los árboles.
“ah3”, fui al campus de Facebook, pero me sacaron, no me dejaron ni mirar jaja.

Como conclusión les puedo decir que, nunca abandonen sus sueños, se pueden hacer realidad en un abrir y cerrar de ojos.

Como hacer backups completos en linux

A veces, nos encontramos con la necesidad de hacer una copia de seguridad a un equipo linux, ya sea desktop o server, podemos backupear directorios importantes, o hacer un full backup de todo el sistema.

Siempre es recomendable hacer backups, de todo, aunque no sea lo mas divertido hacerlos, créeme, la perdida de información es una de las peores catástrofes en el mundo informático, no solo la pérdida de datos en una DB, sino también podemos perder código/scripts/diseños etc, que nos demandará tiempo volver a escribir.

La forma más fácil de hacer un backup de forma manual, es con el comando tar que nos crea un archivo comprimido y es fácil de usar.

La sintaxis es la siguiente:

tar [parámetros]

Las operaciones mas usadas son:

  • -z: Comprime usando gzip
  • -c: Crea el archivo
  • -v: Verbose mode. (Muestra el progreso mientras se crea el archivo)
  • -f: Para indicar el nombre del archivo
  • -p Conserva los permisos de los archivos
  • -x Extraer

Ejemplo de uso:

Para comprimir un directorio completo, usamos lo siguiente:

tar -zcvf backup-home.tar.gz /home/*  –> Hace un backup de todos los archivos que estan en el directorio home.

Backup del sistema completo:

Si queremos hacer un backup completo del sistema, para que en caso de una perdida total podamos restaurar nuestro servidor de manera completa, datos/programas/configs…  TODO…  debemos ejecutar esto:

tar cvpzf /backup-full.tar.gz –exclude=/proc –exclude=/lost+found –exclude=/backup-full.tar.gz –exclude=/mnt –exclude=/sys –exclude=dev/pts /

Importante: la barra del final “/” no es un error, eso le dice haga el backup desde el root “/”.
Todos esos “exclude” son, como su nombre lo dice, para excluir directorios que el sistema llena con archivos dinamicos, que van a producir errores a la hora del backup, y no son importante.

Como restaurar los backups:

Para restaurar un backup, se utiliza el comando -x

tar -zxvpf /fullbackup.tar.gz  –> extrae los contenidos en el directorio actual, conservando los permisos (-p) .

tar -zxvf backup-home.tar.gz  –> extrae los contenidos en el directorio actual.

tar -zxvf backup-home.tar.gz /home –> extrae los contenidos en /home.