Koha with no barcodes

Traditionally, Koha 3 depends on the items (we call them existencias in spanish) having a barcode in order to uniquely identify each item. Circulation, for example, requires the librarian to scan the barcode of an item in order to circulate it.

At times, this proves inconvenient since lots of biblios (titles, or títulos in spanish) have the same barcode printed on each item (usually the ISBN number) forcing the library to print new unique barcodes (Koha has a nice barcode generator) for each one of the items in existence.

However, it’s usually not feasible to relabel all items with new barcodes, especially if you have millions of items nationwide. So, I thought of an easy patch to Koha that allows to circulate items based on the item number, and not the barcode.

First of all, you should set the barcode number for each item equal to the item number for those items where you don’t have any barcode recorded. These is best accomplished after loading MARC records on the database using the MySQL console:

  UPDATE items SET barcode = itemnumber; -- optionally using something like WHERE barcode = ''

On my case, for over 1.1 million items, it took some 3 minutes 6 seconds to complete. There’s a drawback, however, because you need to run this periodically as you add more items, but it’s not something your DBA can’t automate. At this point you can circulate items using items number, and you can print barcodes with that number, but it’s still not easy for the librarian to either remember the item number or look it up before circulating.

You can apply an easy patch on line 44 of the modules/catalogue/moredetail.tmpl file of the Intranet, providing a new link on the Items tab of a biblio to start the borrowing workflow for a specific item:

<!-- TMPL_UNLESS NAME="issue" --><a href="/cgi-bin/koha/circ/circulation.pl?barcode=<!-- TMPL_VAR NAME="itemnumber" -->">[Circulate item <!-- TMPL_VAR NAME="itemnumber" -->]</a><!-- /TMPL_UNLESS -->

Of course, circ/circulation.pl on the Intranet also needs a small patch to store the barcode number on the session and then reusing it when the borrower is selected, near line 111:

my $barcode;
if ( $session->param('barcode') ) {
  $barcode = $session->param('barcode');
  $session->clear('barcode');
} elsif ( $query->param('barcode') ) {
  $barcode = $query->param('barcode') || '';
  $session->param('barcode', $barcode);
}

$barcode =~  s/^\s*|\s*$//g; # remove leading/trailing whitespace
...

Restart your Web server and that’s it. You can now search for a biblio, go to the Items tab, select an item to be circulated, select a borrower, and the item is circulated. For returns, search for the user and go to the end of the page, you can see all items on circulation, fines and return options. The workflow changes a little bit, but it’s the easiest way I’ve devised to operate a Koha ILS when barcodes are absent or outside your control.

Stressting LDAP for fun and profit

Hace casi dos años, el grupo de Software Libre de PDVSA, la petrolera estatal venezolana, estaba indeciso sobre la utilización de OpenLDAP como solución de directorio compatible con LDAPv3 que les permitiera sustituir parcial o totalmente Microsoft Active Directory. Ellos/as me plantearon su mayor duda: ¿qué tan robusto es OpenLDAP en entornos empresariales?

Yo ya había utilizado OpenLDAP extensivamente en entornos empresariales, incluso en convivencia con Microsoft Active Directory cuando trabajé en el Centro de Cómputo de EDELCA, la principal empresa eléctrica en Venezuela. Ese setup ahora está utilizando 389 (antes Fedora Directory Server) para sincronizar claves con MSAD para más de 17 mil personas y centenares de miles de objetos, y a pesar de que lo había visto funcionar bajo cargas muy inusuales, y usábamos clusters con distribución geográfica, yo también tenía la duda de los números.

Decidí probar esto en varios frentes. El primero, generar LDIFs muy grandes, y averiguar cuánto representarían en disco una vez almacenados en Berkeley DB, suponiendo que se fuera a utilizar ese popular backend para OpenLDAP. Esto nos daría una idea, exceptuando índices y optimizaciones, de los requerimientos en memoria de un producto como OpenLDAP en escenarios muy grandes. El segundo frente estaba limitado a las pruebas de tiempo de respuesta de la aplicación ante escenarios de búsqueda.

La razón por la que pruebo búsquedas es porque esta es la aplicación más común de un directorio LDAP. Mis pruebas fueron locales usando TCP/IP porque, aparte de contaminar los tiempos, en mi experiencia los tiempos de respuesta de un directorio LDAP, cuando está networked, se diluyen en los tiempos de procesamiento de la aplicación; en EDELCA preparé un informe que estudiaba la razón de un problema con GNOME Evolution en el cual el autocompletado tardaba varios segundos en responder, aunque en trazas de red se evidenciaba que el directorio LDAP había respondido en milisegundos (un bug en la aplicación) Imagine otro escenario: en la firma y la comprobación de firma de una infraestructura de llave pública dependiente de LDAP, ¿será el tiempo de respuesta de LDAP determinante cuando estamos potencialmente sobrecargando al cliente calculando hashes para la firma de un documento?

Por esto, el tiempo de respuesta local vs. networked no sería un indicativo de la experiencia del usuario final con el directorio, en parte el motivo de la preocupación del cliente. Además hay proxys en LDAP (montamos uno en PDVSA CRP, junto con Penrose como integrador de directorios, o como lo llaman en el mercado, un virtual directory) y estrategias de infraestructura para atacar esto de manera estructural.

Como no quería pasar seis meses de mi vida diseñando experimentos y ya que supuse que nadie iba a estar demasiado interesado en los resultados, dejé de lado algunos escenarios de borde y preparé un par de scripts, en Perl como es natural, para escribir LDIFs muy rápido y para consultar LDAPs muy rápido. Utilicé threads en Perl para su implementación (quería consultar en paralelo), aunque estoy prácticamente convencido por Luis Muñoz, Alejandro Imass y Ernesto Hernández-Novich de no repetir el uso de threads jamás.

De allí nació un pedestrian LDAP tester, que luego extendí a un stresster de MTAs a través de SMTP y a uno de procesos de autenticación en aplicaciones Web utilizando WWW::Mechanize. Esto es porque el proyecto con PDVSA involucraba las tres cosas: implementamos un cluster de Zimbra Collaboration Suite Community y 389 (FDS) utilizando Debian GNU/Linux en ocho servidores con Xen y NFSv3 para un poco más de 25 mil cuentas, a la fecha 18 meses en producción. Voy a subir los otros scripts pronto, pero vamos con los findings de LDAP, de mi nota informal de Julio 2008:

Cargar 200 mil registros (un LDIF de 66 MB.) en la base de datos tiene una representación en BDB de unos 928 MB. Asumamos que requeriríamos, worst case scenario ese espacio en memoria, sin índices ni los registros de replicación. Cargar esos 200 mil registros en OpenLDAP (slapadd) se toma entre 135 y 180 minutos aproximadamente. Afortunadamente la carga se hace sólo una vez. Indizar la base de datos (slapindex) es relativamente rápido y el tiempo se mantiene igual con 1 mil o 200 mil registros. Esto es lógico y bueno.

Hice una búsqueda similar a una consulta de una libreta de direcciones popular. Esa búsqueda generó 15 mil resultados (el número de resultados se puede restringir en OpenLDAP, pero a efectos de la prueba lo desactivé) — en mis pruebas, 100 clientes consultando paralelamente por TCP/IP y obteniendo los 15 mil resultados, sin limitar los atributos más allá de las ACLs se tomó:

real    0m12.048s
user    0m10.413s
sys     0m0.768s

Lo cual es más que aceptable. Ahora bien, el uso en memoria RAM del servicio está en los 640 MB. de memoria virtual y casi 200 MB. en memoria residente. Esto representa el 15% de la memoria del servidor que utilicé, un HP Integrity descontinuado para 2008, que tiene 2 GB. de RAM. Al introducir índices, estos se cargan en memoria, así como el número de objetos que se quiere tener en caché (yo coloqué 50 mil) por lo que habría que pensar en al menos unos 4 GB. de RAM para un volumen así.

Creando índices para los atributos cn, sn, givenName y mail, y ajustando un poco los parámetros de configuración de BerkeleyDB (tabulado del FAQ-O-Matic) la indización se toma un tiempo bastante mayor, para un tiempo de respuesta con la misma búsqueda no mucho menor:

real    0m10.540s
user    0m8.593s
sys     0m0.480s

Por lo que, en general, optimizar poco los índices tendría mucho impacto en las cargas y poco impacto en las búsquedas: si vas a indizar, indiza todo lo que quieras y puedas. Lo otro crítico de optimizar es el DBCONFIG; para 2008, y gracias en parte al oscurantismo de Oracle, era más arte que ciencia. El FAQ-O-Matic de OpenLDAP, como siempre, da la mejor información, ver los artículos 1072, 1075, 42, 738 y 893.

Now for the goodies, hay tres scripts que publiqué en mi carpeta de CPAN. El primero genera LDIFs, de forma muy básica, entradas tipo libreta de direcciones (plt-ldif-generator); el segundo hace las consultas con threads (plt-bambam) y el tercero es una muestra del patrón de uso del tester (plt-usage-pattern), en shell script.

Necesita un Perl razonablemente moderno, compilado con soporte para threads, la librería de Threads de Perl y el módulo Net::LDAP. Un aptitude install libthreads-perl libnet-ldap-perl es suficiente. El primer script también espera que usted provea un root.ldif (el nombre se puede cambiar en el código) con la entrada base del directorio (dc=foo,dc=bar; por ejemplo) y un model.ldif que tenga la entrada modelo con tags que el script sustituirá (obvio, puede usar un LDIF prehecho), algo como:

dn: cn=#CN#,dc=foo,dc=bar
objectClass: top
objectClass: inetorgperson
uid: #USER#
cn: #CN#
sn: #SN#
givenName: #FN#

Importante: como ya dije antes, algunos casos de uso están fuera de la prueba que realicé, en la mayoría de los casos porque no impactan la prueba sensiblemente (por ejemplo, autenticar las conexiones usando credenciales, cifrado con TLS/SSL y otros, no sería un problema grave) aunque sí podría nombrar algunos elementos que influyen en el performance cuando se lleva OpenLDAP a la práctica en escenarios distintos al trivial, como por ejemplo el uso de muchos schemas y de atributos divergentes en las entradas del directorio, sobrecargar el servicio con temas administrativos como largas ACLs dinámicas y sobre todo altos niveles de verbosidad y overlays de terceros. Afortunadamente, ninguno de estos es un must para un setup grande de LDAP. El código de mis scripts también debe tener bugs y está terriblemente documentado, pero le cumple el propósito perfectamente.

Este blog refleja única y exclusivamente mis opiniones, y no las de mis empleadores, las de las organizaciones de las que formo parte ni las de ninguna otra persona natural o jurídica, pública o privada, nacional o extranjera.