Len Cardinal, Consultor senior, Microsoft Consulting Services
George V. Reilly, Responsable de rendimiento de Microsoft IIS
Adaptación de un artículo
(en inglés) de Nancy Cluts
Ingeniero de tecnología de desarrolladores
Microsoft Corporation
Resumen: este artículo presenta una serie de sugerencias para la
optimización
de las aplicaciones ASP y VBScript.
Contenido
Introducción
Sugerencia 1:
Almacenar en caché datos utilizados frecuentemente en el servidor Web
Sugerencia 2:
Almacenar en caché datos utilizados frecuentemente en el objeto de aplicación o
de sesión
Sugerencia 3:
Almacenar en caché datos y HTML en los discos del servidor Web
Sugerencia 4:
Evitar almacenar en caché componentes no ágiles en los objetos de aplicación o
de sesión
Sugerencia 5:
No almacenar en caché conexiones de la base de datos en los objetos de
aplicación o de sesión
Sugerencia 6:
Utilizar prudentemente el objeto de sesión
Sugerencia 7:
Encapsular el código en los objetos COM
Sugerencia 8:
Adquirir los recursos en el último momento y liberarlos cuanto antes
Sugerencia 9:
La ejecución fuera de proceso permite el equilibrio entre el rendimiento y la
confiabilidad
Sugerencia
10: Utilizar Option Explicit
Sugerencia
11: Utilizar variables locales en las subrutinas y las funciones
Sugerencia
12: Copiar datos utilizados frecuentemente en las variables de la secuencia
Sugerencia
13: Evitar redimensionar las matrices
Sugerencia
14: Utilizar el búfer de respuesta
El rendimiento es una característica
fundamental y, de hecho, si una aplicación no se diseña desde un principio
teniéndolo en cuenta, más adelante será preciso volver a desarrollarla. Dicho
de esta manera uno se pregunta: ¿existe alguna estrategia eficaz para maximizar
el rendimiento de la aplicación ASP (Páginas Active Server)?
Este artículo proporciona una serie de
sugerencias que permiten optimizar las aplicaciones ASP y Visual Basic®
Scripting Edition (VBScript) y en él se discuten las muchas trampas y
dificultades a las que se ha tenido que hacer frente. Las sugerencias que se
enumeran se han utilizado en distintos sitios, incluyendo el de Microsoft,
http://www.microsoft.com, y se ha comprobado que funcionan a la perfección.
Este artículo da por sentado que el lector conoce los aspectos básicos del
desarrollo de ASP, incluidos VBScript y/o JScript, las aplicaciones y sesiones
ASP y el resto de objetos intrínsecos ASP (Request, Response y Server).
A menudo el rendimiento de ASP depende de
mucho más que el propio código ASP. En lugar de cubrir todo el tema en un solo
artículo, se han enumerado al final del mismo los recursos relacionados con el
rendimiento. Estos vínculos tratan sobre temas relacionados o no con ASP, por
ejemplo, Objetos de datos ActiveX® (ADO), Modelo de objetos componentes (COM),
bases de datos y la configuración de Internet Information Server (IIS). Se
trata de algunos de nuestros vínculos preferidos y recomendamos que los visite.
Una página ASP típica recupera los datos
desde el almacenamiento central de datos y, a continuación, convierte los
resultados en lenguaje de marcado de hipertexto (HTML). Independientemente de
la velocidad de la base de datos, recuperar los datos desde la memoria resulta
un proceso mucho más rápido que recuperarlos desde dicho almacenamiento central
de datos. La lectura de los datos en un disco local también se lleva a cabo más
rápidamente que si se recuperan desde la base de datos. Por otra parte, también
se puede incrementar el rendimiento almacenando los datos en caché en el
servidor Web, ya sea en la memoria o en el disco.
El almacenamiento en caché constituye un
mecanismo clásico que permite el equilibrio entre el espacio y el tiempo. Si se
almacenan los elementos adecuados, se puede observar un impresionante aumento
en el rendimiento. Para que la caché resulte efectiva ésta debe almacenar los
datos que se vuelven a utilizar con relativa frecuencia y aquellos que pueden
resultar (moderadamente) costosos de actualizar. Contar con datos obsoletos en
la caché supone malgastar la memoria.
Los datos que no cambian con excesiva
frecuencia son los candidatos idóneos para que se almacenen en caché, ya que
uno no debe preocuparse por la sincronización con la base de datos después de
un tiempo; listas de cuadros combinados, tablas de referencia, recortes DHTML,
cadenas de lenguaje de marcado extensible (XML), elementos de menú y variables
de configuración de los sitios (incluyendo nombres de origen de datos (DSN),
direcciones de protocolo de Internet (IP) y rutas Web). Sin embargo, es
necesario señalar que lo que se almacena en caché es la presentación de
los datos y no los datos en sí. Si una página ASP no se modifica con demasiada
frecuencia y resulta costoso almacenarla en caché (por ejemplo, un catálogo
completo de productos), se debe considerar la posibilidad de generar
previamente el HTML, en lugar de volver a escribirlo para cada solicitud.
¿Dónde deben almacenarse los datos y cuáles
son las principales estrategias para el almacenamiento en caché? Por regla
general los datos se almacenan, bien en la memoria, bien en los discos del
servidor Web. Las dos sugerencias que se proporcionan a continuación cubren
ambas opciones.
Los objetos Application y Session
ASP resultan unos contenedores adecuados para el almacenamiento de los datos,
que se pueden asignar tanto al objeto Application como a Session
y que permanecen en la memoria entre solicitudes HTTP. Los datos de la sesión
se almacenan por usuario, mientras que los de la aplicación los comparten todos
los usuarios.
¿En qué punto suele cargar los datos en la
aplicación o la sesión? Con frecuencia los datos se cargan durante el inicio de
la sesión o de la aplicación, para lo que es necesario agregar el código
adecuado a Application_OnStart() o Session_OnStart()respectivamente. Estas funciones deben encontrarse en
Global.asa, aunque si no es así, siempre se podrán agregar. Los datos también
se pueden cargar cuando se los necesita por vez primera. Para ello, agregue el
código (o escriba una función de secuencia reutilizable) a la página ASP de
forma que compruebe la existencia de los datos y los cargue, si fuera
necesario. Este ejemplo ilustra la técnica clásica de mejora del rendimiento
conocida como evaluación perezosa, es decir, no se calcula nada hasta
que no se sabe que se necesita. Un ejemplo:
<%Function GetEmploymentStatusList Dim d d = Application("EmploymentStatusList")If d = "" Then
' La función FetchEmploymentStatusList (no se muestra)
' busca los datos en la BD y devuelve una matriz
d = FetchEmploymentStatusList()
Application("EmploymentStatusList") = d End If GetEmploymentStatusList = dEnd Function
%>
Se podrían emplear funciones similares con
cada grupo de datos necesario.
¿En qué formato deben almacenarse los datos?
Se puede almacenar cualquier tipo Variant, ya que se trata de variables de
secuencia. Por ejemplo, se pueden almacenar cadenas, enteros o matrices. A
menudo se almacenará el contenido de un recordset ADO en uno de estos tipos de
variables. Para extraer los datos del recordset ADO, se pueden copiar
manualmente en las variables VBScript, un campo por vez. Resulta más rápido y
sencillo utilizar una de las persistentes funciones del recordset ADO GetRows(),GetString()
(en inglés) o Save()
(ADO 2.5) (en inglés). No es el objetivo de este artículo proporcionar todos
los detalles sobre este tema, sin embargo, sí que se ofrece un ejemplo
ilustrativo sobre cómo utilizar GetRows()
para devolver una matriz de datos del recordset:
' Obtener Recordset, devolver como matriz
Function FetchEmploymentStatusList Dim rs Set rs = CreateObject("ADODB.Recordset") rs.Open "seleccionar StatusName, StatusID en EmployeeStatus", _ "dsn=employees;uid=sa;pwd=;" FetchEmploymentStatusList = rs.GetRows() " Devolver datos como matriz rs.Close Set rs = NothingEnd Function
Un refinamiento aún mayor sería almacenar en
caché el HTML de la lista en lugar de la matriz. Aquí se muestra un sencillo
ejemplo de cómo hacerlo:
' Obtener Recordset, devolver como lista de opción HTML
Function FetchEmploymentStatusList Dim rs, fldName, s Set rs = CreateObject("ADODB.Recordset") rs.Open "seleccionar StatusName, StatusID en EmployeeStatus", _ "dsn=employees;uid=sa;pwd=;" s = "<seleccionar nombre=""EmploymentStatus">" & vbCrLf Set fldName = rs.Fields("StatusName") ' Enlace de campos ADO Do Until rs.EOF' La siguiente línea viola Evitar la concatenación de las cadenas...,
' pero no importa ya que estamos creando una caché
s = s & " <option>" & fldName & "</option>" & vbCrLf
rs.MoveNext Loop s = s & "</select>" & vbCrLfrs.Close
Set rs = Nothing ' Consultar Adquirir los recursos en el último...
FetchEmploymentStatusList = s ' Devolver datos como cadena
End Function
En las condiciones adecuadas se pueden almacenar en caché los propios recordsets
ADO en el ámbito de aplicación o de sesión, con la salvedad de dos casos:
Si no se puede garantizar que se van a
cumplir dichos requisitos, los recordsets ADO no se deben almacenar en caché.
En la sugerencias Evitar almacenar
en caché componentes no ágiles... y No almacenar en
caché conexiones... que aparecen a continuación se discuten los principales
riesgos que supone el almacenamiento de objetos COM en el ámbito de aplicación
o de sesión.
En esta circunstancia, los datos permanecen
allí hasta que el desarrollador decide modificarlos con la programación, hasta
que termina la sesión o hasta que se reinicia la aplicación Web. ¿Qué ocurre si
es necesario actualizar los datos? Para forzar manualmente una actualización de
los datos de la aplicación, se puede llamar a una página ASP a la que sólo
tenga acceso el administrador para que actualice los datos. Otra alternativa es
actualizarlos automáticamente de forma periódica utilizando una función. El
ejemplo siguiente almacena una marca de tiempo con los datos y los actualiza
después de un intervalo de tiempo concreto.
<%
' error no mostrado...
Const UPDATE_INTERVAL = 300 ' Actualizar intervalo, en segundos
' Función para devolver lista de estado
Function GetEmploymentStatusList UpdateEmploymentStatus GetEmploymentStatusList = Application("EmploymentStatusList")End Function
' Actualizar periódicamente los datos en caché
Sub UpdateEmploymentStatusList Dim d, strLastUpdate strLastUpdate = Application("LastUpdate") If (strLastUpdate = "") Or _ (UPDATE_INTERVAL < DateDiff("s", strLastUpdate, Now)) Then ' Nota: se pueden obtener dos o más llamadas. Es suficiente y simplemente
' resultará en varias búsquedas innecesarias (existe una solución)
' La función FetchEmploymentStatusList (no se muestra)
' busca los datos en la BD y devuelve una matriz
d = FetchEmploymentStatusList()
' Actualizar el objeto de aplicación. Utilizar Application.Lock()
' para garantizar la coherencia de los datos
Application.Lock
Application("EmploymentStatusList") = d Application("LastUpdate") = CStr(Now) Application.Unlock End IfEnd Sub
Para ver otro ejemplo consulte World’s
Fastest ListBox with Application Data (en inglés).
Es necesario advertir que almacenar en caché
matrices de gran tamaño en los objetos Session o Application no
es muy recomendable. Antes de poder tener acceso a cualquiera de los elementos
de la matriz, la semántica de los lenguajes de secuencias requiere que se
realice una copia temporal de la matriz completa. Por ejemplo, si se almacena
en caché una matriz de cadenas de 100.000 elementos que asigne códigos postales
norteamericanos a estaciones meteorológicas locales en el objeto Application,
ASP debe copiar primero las 100.000 estaciones meteorológicas antes de que se
pueda extraer una sola cadena. En este caso, resultaría mucho mejor elaborar un
componente personalizado con un método también personalizado que permitiera
almacenar las estaciones, o bien, utilizar uno de los componentes
de diccionario.
Un comentario adicional en la misma línea de
no tirar las frutas frescas con las pochas: las matrices proporcionan un
mecanismo rápido para la consulta y el almacenamiento de datos clave que son
contiguos en la memoria. La indización de un diccionario constituye un proceso
más lento que la de una matriz. Se debe seleccionar la estructura de datos que
ofrezca el mejor rendimiento en cada caso.
Puede que en ocasiones el volumen de datos
que se deben almacenar en la memoria caché sea demasiado elevado. No obstante,
el término “demasiado” constituye una apreciación, ya que depende de la
cantidad de memoria que se desea utilizar, así como del número de elementos que
se quieren almacenar en caché y la frecuencia en la que dichos elementos se
recuperan. En cualquier caso, si son demasiados los datos que se deben guardar,
tenga en cuenta que siempre podrá almacenarlos en archivos XML o de texto en
los discos duros de los servidores Web. Puede combinar el almacenamiento en
discos y en memoria para crear una óptima estrategia de almacenamiento en caché
para su sitio.
Hay que tener en cuenta que cuando se está
analizando el rendimiento de una única página ASP, la recuperación de los datos
del disco puede que no resulte una operación más rápida que su recuperación
desde la base de datos. Sin embargo, el almacenamiento en caché reduce la carga
que deben soportar la base de datos y la red. En condiciones de cargas mayores
se mejorará considerablemente el rendimiento general. El almacenamiento en
caché puede ser muy útil cuando se desean almacenar los resultados de una
consulta costosa, como es el caso de las tablas múltiples, de procedimientos
almacenados más complejos o de grandes conjuntos de resultados. Pero, como
siempre, se deben comparar otros esquemas.
ASP y COM proporcionan algunas herramientas
que permiten construir esquemas de almacenamiento basados en discos. Las
funciones Save() y Open() del recordset ADO guardan y cargan los
recordsets desde el disco. Se pueden utilizar ambos métodos para volver a
escribir el código de muestra de la anterior sugerencia, Almacenar en
caché datos utilizados frecuentemente en el objeto de aplicación...,
sustituyendo Save() por el código que desarrolla el objeto Application.
Existen otros componentes que funcionan con
archivos:
Por último, otra alternativa posible es el
almacenamiento en caché de la presentación de los datos en un disco en lugar de
los propios datos. El HTML procesado previamente puede almacenarse en disco
como un archivo de extensión .htm o .asp, archivos a los que los hipervínculos
pueden conducir directamente. Se puede automatizar el proceso de generación de
HTML utilizando algunas de las herramientas del mercado como XBuilder (en inglés) o las
características para la publicación en Internet de Microsoft® SQL Server™. Otra
opción es #incluir miniprogramas de HTML en un archivo .asp. También se pueden
leer los archivos HTML desde el disco utilizando FileSystemObject, o
bien, emplear XML
para el procesamiento previo (en inglés).
Mientras que el almacenamiento en caché de
los datos en el objeto Application o Session puede ser una
excelente idea, el mismo tipo de almacenamiento de objetos COM puede
suponer ciertas dificultades. Puede resultar tentador guardar objetos COM
utilizados frecuentemente en los objetos Application o Session.
Desafortunadamente, muchos de estos objetos COM, incluyendo aquellos
desarrollados en Visual Basic 6.0 o posterior, pueden ocasionar serios cuellos
de botella cuando se almacenan en los objetos Application o Session.
Más específicamente, cualquier componente que
no sea ágil
(en inglés) puede producir cuellos de botella en los objetos Session o Application.
Un componente ágil es un componente marcado como ThreadingModel=Both que agrega el ordenamiento de subprocesamiento libre
(FTM) o un componente marcado como ThreadingModel=Neutral. (El modelo Neutral es una novedad en Windows® 2000 y
COM+). Los siguientes componentes no son ágiles:
Los componentes
configurados (Microsoft Transaction Server (MTS)/biblioteca COM+ y
aplicaciones/paquetes del servidor) no son ágiles a menos que se consideren
Neutral. Los componentes de subprocesamiento controlado, entre otros
componentes no ágiles, ofrecen mejores resultados en el ámbito de página (es
decir, se crean y se destruyen en una sola página ASP).
En IIS 4.0, un componente marcado como ThreadingModel=Both se consideraba ágil. Esto ya no es suficiente en IIS
5.0. El componente no sólo debe marcarse como Ambos, sino que debe agregar el
FTM. El artículo
sobre agilidad (en inglés) describe cómo hacer que los componentes de C++
escritos con la biblioteca de plantillas activa (ATL) agreguen el FTM. Tenga en
cuenta que si el componente almacena en caché los punteros de la interfaz,
también dichos punteros deben ser ágiles o deben almacenarse en la tabla de
interfaz global COM (GIT). Si no se puede volver a compilar un componente
marcado como Ambos para agregar el FTM, se podrá marcar dicho componente como ThreadingModel=Neutral. De forma alternativa, si no desea que IIS realice la
comprobación de la agilidad (es decir, desea que los componentes no ágiles se
almacenen en el ámbito de aplicación o de sesión), puede establecer AspTrackThreadingModel como True en
la metabase. Cambiar AspTrackThreadingModel
no es una operación muy recomendable.
IIS 5.0 enviará un mensaje de error si se
intenta almacenar un componente no ágil creado con Server.CreateObject en el objeto Application. Se puede solucionar
el problema utilizando <object runat=server scope=application ...> en Global.asa, aunque no es recomendable, ya
que conduce al ordenamiento y serialización, como se explica a continuación.
¿Qué es lo que ocurre si se almacenan en
caché componentes no ágiles? Un componente de este tipo almacenado en el objeto
Session “bloqueará” la sesión a un subproceso de trabajo ASP. ASP
mantiene un conjunto de procesos de trabajo que se ocupan de las solicitudes.
Normalmente el primer subproceso de trabajo que estuviera libre se ocuparía de
la nueva solicitud. Si se bloquea una sesión a un subproceso, la solicitud debe
esperar hasta que el subproceso asociado se encuentre disponible. A continuación
presentamos una analogía que bien puede servir para explicarlo: uno va al
supermercado, selecciona varios tipos de artículos comestibles y abona el
importe en la caja nº 3. Imagine que a partir de ese momento, siempre que
compre artículos en ese supermercado, debe abonarlos en la misma caja nº 3,
aunque haya menos personas esperando en otras cajas o algunas de ellas se
encuentre vacía.
El almacenamiento de componentes no ágiles en
el ámbito de aplicación tiene un efecto aún peor sobre el rendimiento. ASP debe
crear un subproceso espacial para ejecutarlos, lo que tiene dos consecuencias:
todas las llamadas deben realizarse en serie y se deben ordenar en dicho
subproceso. El ordenamiento supone que los parámetros deben almacenarse
en un área compartida de memoria; se realiza un costoso cambio de contexto en
el subproceso especial; se ejecuta el método del componente; se ordenan los
resultados en el área compartida; y un cambio adicional de contexto devuelve el
control al subproceso original. La serialización implica que los métodos
se ejecutan de uno en uno. No se puede dar la circunstancia de que dos
subprocesos de trabajo ASP ejecuten simultáneamente métodos en el componente
compartido, ya que esto acabaría con la concurrencia, especialmente en equipos
con varios procesadores. Y aún peor, todos los componentes no ágiles en el
ámbito de aplicación comparten un subproceso (“Host STA”), de forma que los
efectos de la serialización son aún más marcados.
¿Resulta demasiado confuso? Aquí tiene
algunas reglas generales. Si está desarrollando objetos en Visual Basic (6.0) o
posterior, no debe almacenarlos en caché en los objetos Application o Session.
Si desconoce el modelo de subprocesamiento de un objeto, es mejor que no lo
almacene. En lugar de almacenar objetos no ágiles debe crearlos y liberarlos en
cada página. Los objetos se ejecutarán directamente en el subproceso de
trabajo, con lo que no se producirán ni la serialización ni la ordenación. El
rendimiento será el adecuado si los objetos COM se ejecutan en un cuadro de IIS
y si no tardan demasiado en iniciarse y destruirse. Tenga en cuenta que los
objetos de subprocesamiento único no deben utilizarse de este modo. Preste
atención: ¡VB
puede crear objetos de subprocesamiento único! Si debe utilizar objetos de
subprocesamiento único de esta manera (como una hoja de cálculo de Microsoft
Excel), no espere un buen rendimiento.
Los recordsets ADO pueden almacenarse en
caché de forma segura cuando ADO se marca como de subprocesamiento libre. Para
marcar ADO como de subprocesamiento libre, utilice el archivo Makfre15.bat, que
se ubica normalmente en el directorio \\Archivos de programa\Archivos comunes\System\ADO.
Advertencia: ADO no debe marcarse como de subprocesamiento libre si
no se utiliza Microsoft Access como base de datos. El recordset ADO siempre
debe estar desconectado. Por regla general, si no se puede controlar la
configuración de ADO del sitio (por ejemplo, el caso de los fabricantes
independientes de software [ISV], que distribuyen aplicaciones Web a los
clientes y éstos de ocupan de sus propias configuraciones), probablemente sea
mejor que no almacene en caché los recordsets.
Los componentes
de diccionario también se incluyen en la categoría de objetos ágiles. El
objeto LookupTable carga sus datos desde el archivo de datos y resulta muy útil
para los datos de cuadros combinados y como información de configuración. PageCache
de Duwamish
Books (en inglés) proporciona la semántica del diccionario, al igual que el
diccionario Caprock. Estos objetos, o derivados, pueden formar la base de una
eficaz estrategia de almacenamiento. Tenga en cuenta que el objeto Scripting.Dictionary
NO es ágil y que no debe almacenarse en el ámbito de aplicación o de sesión.
Almacenar en caché conexiones ADO no es por
lo general una buena estrategia. Si un objeto Connection se almacena en
el objeto Application y se utiliza en todas las páginas, éstas
competirán por utilizar la conexión. Si el objeto Connection se almacena
en el objeto Session ASP, se creará una conexión a la base de datos para
cada usuario. Con esto se pierden los beneficios que suponía la agrupación de
conexiones y se incrementa en exceso la carga tanto en el servidor Web como en
la base de datos.
En lugar de almacenar las conexiones a la
base de datos, una alternativa mejor es crear y destruir los objetos ADO en
cada una de las páginas ASP que los utilizan. Se trata de un mecanismo muy
eficaz ya que IIS incorpora la agrupación de conexiones. De forma más precisa,
IIS habilita automáticamente la agrupación de conexiones de OLEDB y ODBC. Así
se garantiza que la creación y posterior destrucción de las conexiones de cada
página ofrece resultados positivos.
Dado que los recordsets almacenan una
referencia a la conexión a la base de datos, no se deben almacenar en caché en
el objeto Application o Session. No obstante, sí que se pueden
almacenar en caché de forma segura los recordsets desconectados, ya que
estos últimos no guardan referencia alguna a la conexión de datos. Para
desconectar un recordset, siga los dos pasos siguientes:
Set rs = Server.CreateObject("ADODB.RecordSet")
rs.CursorLocation = adUseClient ' paso 1
' Poblar el recordset con datos
rs.Open strQuery, strProv
' desconectar el recordset del proveedor de datos y el origen de datos
rs.ActiveConnection = Nothing ' paso 2
Puede obtener más información sobre la
agrupación de conexiones en las referencias a ADO y SQL
Server.
Después de haber roto una lanza en favor de
las virtudes del almacenamiento en caché en las aplicaciones y en las sesiones,
vamos a sugerir evitar el objeto Session. Las sesiones pueden suponer
ciertas dificultades cuando se utilizan en sitios con mucha actividad,
como veremos más adelante. Por sitios con mucha actividad generalmente
entendemos aquellos que solicitan cientos de páginas por segundo o los que
visitan simultáneamente miles de usuarios. Esta sugerencia nos parece aún más
importante en el caso de los sitios que se deben escalar horizontalmente, es
decir, aquellos que utilizan varios servidores para acomodar la carga o para
implementar la tolerancia a los errores. En sitios más pequeños, como los de
una intranet, las ventajas que proporcionan las sesiones bien compensan los
inconvenientes.
Recapitulemos: ASP crea automáticamente una
sesión para cada usuario que realiza la solicitud al servidor Web. Cada sesión
dispone de casi 10 KB de memoria extra (siempre por encima de la cantidad de
datos almacenados en la sesión) y ralentiza un poco todo el proceso. La sesión
permanece viva durante un período de tiempo de espera configurable, que
generalmente es de 20 minutos.
El principal problema que plantean las
sesiones no es el rendimiento, sino la escalabilidad. Las sesiones no se
extienden a los servidores Web; una vez se ha creado una sesión en un servidor,
sus datos permanecen allí. Esto significa que si se utilizan las sesiones en
una granja Web, se debe diseñar una estrategia para las solicitudes de cada
usuario de forma que siempre se dirijan al servidor en el que existe la sesión
del usuario. Esto se denomina “pegar” un usuario al servidor Web. El término
“sesiones pegadas” deriva de él. Si se produjera algún error en el servidor
Web, se perdería el estado de la sesión de los usuarios que se han “pegado”, ya
que las sesiones no se almacenan en el disco.
Las estrategias de implementación de este
tipo de sesiones incluyen soluciones de hardware y software como el equilibrio
de carga para redes (en inglés) en Windows 2000 Advanced Server y Local
Director de Cisco, que pueden implementarlas, a expensas de cierta
escalabilidad. Estas soluciones no son perfectas. Confirmar una solución de
software propia en este punto no es muy recomendable (solíamos utilizar filtros
ISAPI, la eliminación de URL, etc.).
El objeto Application tampoco se
extiende a los servidores; si es necesario compartir y actualizar los datos de
la aplicación por la granja Web, será preciso utilizar la base de datos
central. No obstante, se debe señalar que los datos de una aplicación de sólo
lectura aún son de utilidad en las granjas Web.
Casi con toda seguridad, en sitios de misión
crítica se deseará distribuir al menos dos servidores Web, aunque simplemente
sea por incrementar el tiempo de actividad (mantenimiento del servidor y
control de errores). Por otra parte, para diseñar una aplicación de misión
crítica se deben implementar las “sesiones pegadas” o simplemente evitar las
sesiones de cualquier tipo, así como cualquier otra técnica de control del
estado que almacene a los usuarios en servidores Web por separado.
Si no desea utilizar sesiones, asegúrese de
desactivarlas. Esta operación se puede realizar, en el caso de la aplicación,
con el Administrador de servicios de Internet (consultar la documentación de
ISM). Si, por el contrario, decide utilizar sesiones, siempre puede minimizar
su impacto en el rendimiento de varias formas distintas.
Puede mover el contenido que no requiera
sesiones (por ejemplo, pantallas de ayuda, áreas de visitantes, etc.) a otra
aplicación ASP en la que se hayan desactivado las sesiones. Utilizando el
esquema de página a página, se sugiere a ASP que el objeto de sesión no es
necesario en una página concreta; utilice la directiva siguiente situada en la
parte superior de la página ASP:
<% @EnableSessionState=False %>
Una buena razón por la que conviene utilizar
esta directiva es que las sesiones crean un interesante problema con los
conjuntos de marcos. ASP garantiza que sólo se ejecutará una solicitud de la
sesión por vez. De esta manera, si el explorador solicita varias páginas para
un sólo usuario, la sesión sólo se ocupará de una solicitud ASP; con ello se
evitan los problemas ocasionados por el subprocesamiento múltiple cuando se
tiene acceso al objeto Session. Desafortunadamente, se obtiene como
resultado que todas las páginas de un conjunto de marcos aparecen en serie, una
tras otra, en lugar de hacerlo simultáneamente. Puede que el usuario se vea
obligado a esperar algún tiempo hasta que aparezcan todos los marcos. La
moraleja de esta pequeña historia es la siguiente: si ciertas páginas de
conjuntos de marcos no dependen de la sesión, es necesario informar de ello a
ASP utilizando la directiva @EnableSessionState=False.
Como alternativa a la utilización del objeto Session
existen varias opciones que permiten controlar el estado
de la sesión. Con tamaños reducidos (menos de 4 KB), generalmente
recomendamos que se utilicen las cookies, las variables QueryString y las de
formulario oculto. Cuando se trata de tamaños de datos mayores como los de un
carro de la compra, la elección más adecuada es una base de datos central. El
tema de las técnicas de control de estado en las granjas Web ha generado mucha
literatura; se pueden consultar algunas referencias en la sección relativa al estado
de la sesión.
Si se dispone de mucho VBScript o JScript,
siempre se puede mejorar el rendimiento moviendo el código a un objeto COM
compilado. El código compilado generalmente se ejecuta con mayor rapidez que el
interpretado. Los objetos COM compilados pueden tener acceso a otros objetos
COM gracias a un “enlace en tiempo de compilación”, un método más eficaz para
invocar métodos de objetos COM que el de “enlace en tiempo de ejecución” que
emplea la secuencia.
Existen más ventajas (además de la mejora del
rendimiento) al encapsular el código en objetos COM:
Los objetos COM también tienen sus
inconvenientes, incluyendo el tiempo de desarrollo inicial y la necesidad de
nuevas habilidades de programación. Encapsular pequeñas cantidades de
ASP puede ir en detrimento del rendimiento en lugar de mejorarlo. Esto ocurre
normalmente cuando una pequeña cantidad de código ASP se incluye en el objeto
COM. En este caso, la carga que supone crear e invocar el objeto COM supera a
las ventajas de utilizar código compilado. Se trata de una cuestión de ensayo y
error el determinar qué combinación de secuencia ASP y código de objetos COM
genera el mejor resultado en cuanto a rendimiento. Microsoft ha mejorado
enormemente el rendimiento de las secuencias y de ADO en Windows 2000/IIS 5.0
en comparación con Windows NT® 4.0/IIS 4.0. De esta forma, la ventaja del
rendimiento que se disfrutaba al utilizar el código compilado sobre el código
ASP se ha reducido con la introducción de IIS 5.0.
Para conocer algunos interesantes debates
sobre las ventajas e inconvenientes de utilizar objetos COM en ASP, consulte Guía
del componente ASP y Programming
Distributed Applications with COM and Microsoft Visual Basic 6.0 (en
inglés). Si desea distribuir los componentes COM, es importante que realice una
prueba
de carga (en inglés) de los mismos. De hecho, se deberían realizar por
norma pruebas de este tipo en todas las aplicaciones ASP.
Se trata de una pequeña sugerencia. En
general, es mejor adquirir los recursos tarde y liberarlos pronto. Esto se
refiere tanto a objetos COM como a los controladores de archivos y demás
recursos.
Las conexiones y recordsets ADO son los
candidatos idóneos para esta optimización. Cuando haya terminado de utilizar un
recordset, por ejemplo, tras crear una tabla con sus datos, se debe liberar
inmediatamente, en lugar de esperar hasta el final de la página. Establecer la
variable de VBScript como Nothing
es la mejor opción. No permita que el recordset se quede fuera de ámbito.
Libere, además, todos los objetos Command o Connection
relacionados. (No olvide llamar a Close()
en los recordsets o conexiones antes de establecerlos como = Nothing). De esta manera se reduce el intervalo de tiempo
durante el cual la base de datos debe hacer juegos malabares con los recursos y
se libera la conexión de la base de datos a la agrupación de conexiones tan
pronto como es posible.
Tanto ASP como MTS/COM+ presentan una serie
de opciones de configuración que permiten compensar la confiabilidad y el
rendimiento. Es preciso entender dichas compensaciones cuando se está
construyendo y distribuyendo una aplicación.
Las aplicaciones ASP pueden configurarse de
forma que se ejecuten de tres formas distintas. Con IIS 5.0, el “nivel de
aislamiento” de la terminología se ha introducido para describir dichas
opciones. Los tres valores del nivel de aislamiento son bajo, medio
y alto:
Entonces, ¿cuál es la opción más adecuada? En
IIS 4.0 se podía observar un declive en el rendimiento cuando la aplicación se
ejecutaba fuera de proceso. Se ha trabajado a conciencia en este tema en IIS
5.0 y se ha logrado minimizar el coste de la ejecución de las aplicaciones ASP
fuera de proceso. De hecho, en la mayoría de las comprobaciones, las
aplicaciones ASP fuera de proceso en IIS 5.0 se ejecutan más rápidamente que
las aplicaciones en proceso en IIS 4.0. No obstante, las aplicaciones en
proceso generan (con un nivel de aislamiento bajo) el mejor rendimiento
en ambas plataformas. A pesar de esta circunstancia, resulta difícil obtener
beneficios del nivel bajo si la frecuencia de aciertos o la salida
máxima son relativamente bajas. Por tanto, es mejor no alcanzar el nivel de
aislamiento bajo hasta que se necesiten cientos o miles de páginas por
segundo y por servidor Web. Como siempre, se deben comprobar las distintas
configuraciones para determinar las compensaciones se que desean obtener.
Nota: cuando se ejecutan aplicaciones ASP fuera de proceso
(con aislamiento medio o alto), se ejecutan en MTS en NT4 y en
COM+ en Windows 2000. Es decir, en NT4 se ejecutan desde Mtx.exe y, en Windows
2000, por su parte, lo hacen en DllHost.exe. Se pueden ver los procesos en el
Administrador de tareas. También se pueden conocer los detalles de la
configuración de los paquetes MTS o de las aplicaciones COM+ por parte de IIS
para aplicaciones ASP fuera de proceso.
Los componentes COM también presentan tres
opciones de configuración que, sin embargo, no son completamente análogas a las
de ASP. Los componentes COM pueden estar: “no configurados”, configurados como
aplicaciones de biblioteca o configurados como aplicaciones de servidor. No
configurado significa que el componente no se ha registrado con COM+ y que
se ejecutará en el espacio de proceso de quien realizó la llamada, es decir, se
encuentran “en proceso”. Las aplicaciones de biblioteca también se encuentran
en proceso, sin embargo se benefician de los servicios de COM+, incluyendo la
seguridad, las transacciones y el soporte del contexto. Las aplicaciones de servidor
se configuran para ejecutarse en su propio espacio de proceso.
Puede que vea unos beneficios importantes en
el uso de componentes no configurados comparados con las aplicaciones de
biblioteca, lo que sí está claro es que verá unos beneficios mucho mayores en
cuanto al rendimiento si utiliza estas últimas en lugar de las aplicaciones de
servidor. Esto se debe a que las aplicaciones de biblioteca se ejecutan en el
mismo proceso que ASP, mientras que las del servidor lo hacen en su propio
espacio de proceso. Las llamadas de inter-proceso resultan mucho más costosas
que las llamadas en proceso. Asimismo, cuando se pasan datos, por ejemplo,
recordsets, entre procesos, todos ellos se deben copiar entre los dos procesos.
¡He aquí un problema! Cuando se utilizan
aplicaciones de servidor COM, si se pasan objetos entre ASP y COM, es preciso
asegurarse de que dichos objetos implementan el denominado “subprocesamiento
por valor” o MBV, ya que los que lo hacen se copian a sí mismos de un proceso a
otro. Esto resulta mucho mejor que la otra alternativa, que consistiría en que
el objeto debe permanecer en el proceso del creador y que el resto de los
procesos llamarían repetidamente al proceso que se está creando para que
utilice el objeto. Los recordsets ADO desconectados se subprocesarán por valor;
los conectados no. Scripting.Dictionary no implementa MBV y no
debería pasarse entre procesos. Por último, enviamos un mensaje a los
programadores de VB: MBV NO se obtiene pasando el parámetro ByVal. El autor del componente original implementaría MBV.
Si tuviéramos que recomendar una
configuración con una compensación razonable en cuanto a rendimiento y
confiabilidad, sería la siguiente:
Estas son únicamente unas líneas generales;
las empresas dedicadas a ello generalmente ejecutan ASP con un nivel de
aislamiento medio o alto, mientras que los servidores Web de un
único propósito pueden ejecutarse con un aislamiento bajo. Analice las
compensaciones y decida la configuración que se ajusta en mayor medida a sus
necesidades.
Utilice Option Explicit en los archivos .asp. Situada en la parte superior
del archivo .asp, esta directiva fuerza al desarrollador a declarar todas las
variables que se van a emplear. Muchos programadores consideran que es de gran
utilidad para depurar las aplicaciones, ya que evita la posibilidad de escribir
incorrectamente el nombre de una variable o de crear accidentalmente otras
nuevas (por ejemplo, MyXLMString=... en
lugar de MyXMLString=).
Quizás algo aún más importante, resulta que
las variables declaradas son más rápidas que las no declaradas. El tiempo de
ejecución de las secuencias hace referencia por nombre a las variables no
declaradas cada vez que se utilizan. A las variables declaradas, por otra
parte, se asigna un ordinal ya sea durante el tiempo de compilación o el de
ejecución. Por consiguiente, el ordinal hace referencia a las variables
declaradas. Debido a que Option Explicit
fuerza la declaración de las variables, garantiza que todas ellas se declaran
para, de esta forma, facilitar el acceso a las mismas.
Las variables locales son aquellas que se
declaran con las subrutinas y las funciones. Dentro de una subrutina o función,
el acceso a una variable local es mucho más rápido que a una global. La
utilización de las variables también tiende a hacer que el código presente una
mayor claridad, por lo que se recomienda su uso siempre que sea posible.
Cuando se tiene acceso a los objetos COM en
ASP, se deben copiar los datos de objetos utilizados frecuentemente en las
variables de la secuencia. De esta forma se reduce el número de llamadas a los
métodos COM, que resultan relativamente costosos comparados con el acceso a las
variables de las secuencias. Obtener acceso a los objetos Collection y Dictionary
reduce el número de costosas consultas que es preciso llevar a cabo.
Por regla general, si se desea obtener acceso
a datos de objetos más de una vez, éstos se deben introducir en una variable de
secuencia. Los protagonistas principales a los que va dirigida esta
optimización son las variables de solicitud (Form y QueryString). Por ejemplo,
puede que su sitio pase una variable QueryString denominada UserID. Supongamos
que se hace referencia a UserID una docena de veces en una página concreta. En
lugar de llamar a Request("UserID") doce veces, es mejor asignar UserID a una variable en
la parte superior de la página ASP y, a continuación, utilizar dicha variable
por toda la página. De esta manera se evitarán 11 llamadas al método COM.
En la práctica, el acceso a las propiedades o
métodos COM puede resultar costoso en apariencia. A continuación se proporciona
un ejemplo de código muy común (desde el punto de vista sintáctico):
Foo.bar.blah.baz = Foo.bar.blah.qaz(1)
If Foo.bar.blah.zaq = Foo.bar.blah.abc Then ' ...
Esto es lo que sucede cuando se ejecuta el
código:
Foo se
resuelve como un objeto global. bar se
resuelve como miembro de Foo, que
resulta ser una llamada al método COM. blah se
resuelve como miembro de Foo.bar. Este también resulta ser una llamada al método COM. qaz se
resuelve como miembro de foo.bar.blah. Sí, de nuevo una llamada al método COM. Foo.bar.blah.quaz(1). Otra llamada al método COM. ¿Se hace una idea? baz. El sistema desconoce si la llamada a qaz ha cambiado el modelo de objeto, por lo que se
deben volver a realizar los pasos 1-3 para resolver baz. baz como
miembro de Foo.bar.blah. Introduzca la propiedad. zaq. abc. Como se puede ver, se trata de un sistema
terriblemente ineficaz (y lento). La forma rápida de escribir el código en
VBScript es:
Set myobj = Foo.bar.blah ' resolver blah UNA VEZ
Myobj.baz = myobj.qaz(1)
If Myobj.zaq = Myobj.abc Then '...
Si se utiliza VBScript 5.0 o
posterior, se puede escribir utilizando la instrucción With:
With Foo.bar.blah .baz = .qaz(1) If .zaq = .abc Then '......
End With
Tenga en cuenta que esta sugerencia también
se puede aplicar a la programación en VB.
Se debe intentar evitar la redimensión de las
matrices con Redim. Por lo que se refiere al rendimiento, si se dispone
de un equipo restringido por el tamaño de la memoria física, resulta mejor
establecer la dimensión inicial de la matriz en el peor de los casos posible, o
bien, establecerla en la situación más óptima y redimensionar cuando sea
necesario. Esto no significa que simplemente se deben asignar un par de
megabytes de memoria si se sabe que no se va a necesitarla.
El código que aparece a continuación muestra
la utilización gratuita de Dim y Redim.
<%Dim MyArray()Redim MyArray(2)MyArray(0) = "hola"
MyArray(1) = "adiós"
MyArray(2) = "hasta pronto"
...
' otro código en el que es necesario más espacio, entonces ...
Redim Preserve MyArray(5)MyArray(3) = "más material"
MyArray(4) = "aún más material"
MyArray(5) = "todavía mucho más material"
%>
Es mucho más fácil dimensionar la matriz con Dim al tamaño correcto inicialmente (en este caso, 5),
que redimensionarla con Redim para aumentar
su tamaño. Puede que se utilice algo de memoria (si al final no se utilizan
todos los elementos), pero el incremento en la velocidad que se obtiene bien
compensa esta inversión.
Se puede almacenar en búfer la salida de una
página completa activando la opción de “búfer de respuesta”. Se minimiza la
cantidad de veces que es necesario escribir en el explorador y se mejora el
rendimiento general. Cada escritura supone una sobrecarga (tanto en IIS como en
la cantidad de datos que se envían), por tanto, cuantas menos escrituras sea
preciso realizar, mejor. TCP/IP ofrece mejores resultados cuando se envían
varios bloques de datos grandes que cuando se deben enviar muchos bloques
pequeños debido al inicio
lento (en inglés) y al algoritmo
de Nagle (en inglés) (empleados para reducir las sobrecarga de la red).
Existen dos maneras de activar el búfer de
respuesta. En primer lugar, se puede activar para toda la aplicación utilizando
para ello el Administrador de servicios de Internet. Se trata del enfoque
recomendado, por lo que el búfer de respuesta se encuentra activado de forma
predeterminada en las aplicaciones ASP en IIS 4.0 y IIS 5.0. En segundo lugar,
página a página; es decir, se puede activar el búfer de respuesta introduciendo
la siguiente línea de código en la parte superior de la página ASP:
<% Response.Buffer = True %>
Esta línea de código debe ejecutarse antes de
que se escriba cualquier dato de repuesta en el explorador (es decir, antes de
que aparezca el HTML en la secuencia ASP y antes de que se envíe cualquier
cookie con la colección Response.Cookies).
En líneas generales, resulta mucho mejor activar el búfer de respuesta en toda
la aplicación, ya que así se evita la necesidad de escribir la anterior línea
de código en cada página.
Uno de los problemas que se plantean con el
búfer de respuesta es los usuarios perciben que la respuesta que obtienen de
las páginas ASP es menor (aunque el tiempo global de respuesta se haya
mejorado) ya que deben esperar a que se genere toda la página antes de poder
ver algo. En el caso de las páginas que tardan en cargarse, se puede activar el
búfer de respuesta con Response.Buffer = False. Sin embargo, una estrategia mucho más eficaz sería utilizar el método
Response.Flush, que descarga todo el HTML de ASP en el explorador.
Por ejemplo, después de que aparezcan 100 filas de una tabla de 1.000, ASP
puede llamar a Response.Flush para que fuerce los resultados y que éstos aparezcan
en el explorador; de esta forma el usuario puede ver las primeras 100 filas
antes de que las restantes estén listas. Esta técnica puede proporcionar además
lo mejor de ambos métodos: el búfer de respuesta combinado con la presentación
gradual de los datos en el explorador.
(Es importante señalar que en el ejemplo
anterior de una tabla de 1.000 filas, muchos exploradores no empezarán a cargar
la tabla hasta que vean la etiqueta de cierre </table>. Compruebe los
exploradores para obtener información sobre compatibilidad. Para solucionar
este problema, intente dividir la tabla en varias más pequeñas con menos filas
cada una de ellas y llame a Response.Flush después de cada tabla. Las últimas versiones de Internet Explorer
mostrarán las tablas antes de descargarlas completamente y lo harán a una
velocidad considerable especialmente si se especifica la anchura de columna
previamente; con esto, Internet Explorer se evita tener que calcular la anchura
de la columna en función del contenido de cada celda).
Otro de los problemas más comunes que
presenta el búfer de respuesta es que puede utilizar gran parte de la memoria
del servidor cuando genera páginas muy extensas. Dejando a un lado la cuestión
de si es recomendable o no generar páginas extensas, también se puede hacer
frente a este problema utilizando Response.Flush.