Mostrando entradas con la etiqueta artículo. Mostrar todas las entradas
Mostrando entradas con la etiqueta artículo. Mostrar todas las entradas

jueves, mayo 14, 2015

Técnicas de programación en VFP: Procesos desatendidos y control de progreso visual

Por: Fernando D. Bozzo

Este es un artículo de una serie que se enfocará en técnicas de programación y optimización en distintas áreas.

Un buen desarrollador asimilará estas técnicas como parte de su forma de trabajo habitual, independientemente de que haga un sistema, ún módulo, una rutina o un programa de pruebas personales, ya que le permitirá programar siempre orientado a la eficiencia, la velocidad de ejecución, la encapsulación, la reutilización y la legibilidad y mantenibilidad del código, o sea, las buenas prácticas.

Al tener en cuenta estas técnicas en cada parte del código y en cada rutina, al final lo que se logra es que el sistema completo esté más optimizado porque sus partes lo están.



   Cuando hablamos de procesos desatendidos, hablamos de procesos que no deben bloquearse esperando la entrada del usuario, son procesos que deben realizar una tarea sin ningún tipo de interacción ni interrupción y que deben controlar tanto la finalización exitosa como la aparición de errores, y en ambos casos no debe haber ningún tipo de mensaje "modal" como un messagbox o form que pida datos de ningún tipo mientras se está ejecutando.

Para esto, es necesario entender la separación de capas (visual, negocio, datos, etc) y el por qué de esa separación (pueden leer un artículo sobre el tema).

Lo primero que a cualquiera no adentrado en el uso de capas (o que las conoce pero es muy cómodo:) se le viene a la cabeza, es hacer un bonito formulario con un botón para procesar, uno para salir y luego, obviamente, un método para hacer el proceso... ¡mal! El proceso conviene que esté aparte, en una librería de clases por ejemplo, o en una librería de procedimientos, pero nunca en el formulario. Este proceso debe hacerse pensando en la reusabilidad, que pueda ser invocado tanto desde un formulario como desde el sistema operativo, si se quiere, o desde otro proceso o programa.



Creando el Proceso Desatendido


Crear un proceso desatendido es realmente simple, pero hay que tener en cuenta algunos pilares fundamentales y respetarlos:

1) Debe estar bien encapsulado: por eso lo ideal es hacerlo basado en una clase de tipo custom o session

2) Debe tener un buen control de errores: esto también es fácil, sobre todo con Try/Catch. Este artículo les explica cómo funciona.

3) No debe contener mensajes de ningún tipo: Ni messagebox, ni wait window, nada, solo el proceso

4) Debe generar una salida de información: Lo habitual es un LOG que luego se pueda consultar o incluso procesar por alguna herramienta para obtener información de estado, pero también debe devolver información de errores, de ocurrir alguno.

5) Opcionalmente puede generar información de avance del proceso

6) Es deseable --aunque no imprescindible-- que también tenga una forma de poder testear el control de errores

7) Opcionalmente puede devolver un código de error DOS para usar con programas externos


¿Parece difícil? Ya van a ver que no lo es para nada.


Primero debemos seleccionar el tipo de clase que vamos a usar: ¿custom o session?

La elección depende de a qué nivel queramos encapsular. La ventaja de la clase session es que está preparada especialmente para usar sesión privada de datos y para ser ligera. La "desventaja" es que no se pueden hacer usando el diseñador de clases, y deben hacerse por código, aunque esto tampoco es un problema.

Por otro lado, la clase custom tiene la ventaja de que se puede hacer usando el diseñador de clases y la desventaja de que es algo más pesada que la session y que no tiene sesión privada de datos, con lo que habrá que tener cuidado al abrir tablas y restaurar áreas de trabajo.

Si el proceso es lo suficientemente complejo como para requerir de más de una clase, una tercer alternativa es hacer una clase session principal que es con la que se interactuará luego, y que esta instancie a las demás clases de tipo custom, con lo que logramos tener una envoltura de sesión de datos privada para todas ellas, aunque entre las clases internas puedan compartir datos.



Veamos un ejemplo práctico simple de un proceso desatendido:

Cálculo que requiere recorrer una tabla y totalizar


Como es un ejemplo, la cantidad de registros no es importante aquí, y el proceso de los datos podrá ser tan complejo como ustedes necesiten, pero a efectos del ejemplo lo vamos a simplificar también.

La tabla la podemos crear con este código desde la ventana de comandos de VFP o con un PRG:

CREATE TABLE T_DATOS ( valor N(10,2) )
FOR I = 1 TO 100
   INSERT INTO T_DATOS (valor) VALUES (I)
ENDFOR
USE


Y la clase la podríamos hacer de esta forma, dentro de lib_proceso.prg, donde intercalaré algunos comentarios para explicar lo que hace y cómo funciona, aunque es muy simple, como puede verse:

*-- lib_Proceso.prg
LPARAMETERS toEx as Exception, tlRelanzarError, tnTotal
#DEFINE CR_LF CHR(13)+CHR(10)
LOCAL loProceso, lnResp
loProceso = CreateObject("c_proceso")
lnResp = loProceso.procesar(@toEx, tlRelanzarError, @tnTotal)
loProceso = NULL
RELEASE loProceso

IF _VFP.StartMode <> 4 OR NOT sys(16) == sys(16,0)
  RETURN lnResp
ENDIF

IF EMPTY(lnResp) && Salida sin error
  QUIT
ENDIF

DECLARE INTEGER OpenProcess IN Win32API INTEGER dwDesiredAccess ;

  , INTEGER bInheritHandle, INTEGER dwProcessID
lnHandle = OpenProcess(1, 1, _VFP.PROCESSID)
DECLARE INTEGER TerminateProcess IN Win32API INTEGER hProcess ;

  , INTEGER uExitCode
=TerminateProcess(lnHandle,1) && Salida con error DOS




La parte anterior es la cabecera del proceso, donde se define la variable del objeto del proceso loProceso y la de respuesta lnResp. En la misma se crea al objeto, que está basado en la clase c_proceso definida bajo este texto, se ejecuta el método Procesar y se guarda el resultado del mismo, que será un código de error, en lnResp.

Con StartMode se determina qué tipo de ejecución estamos haciendo, en este caso solo interesa saber si es desde EXE o APP o si este programa es el principal o no, y por eso se comparan los sys(16) y sys(16,0)

En la última parte de la cabecera, si no hay error simplemente se termina y si hay error (y no es modo desarrollo) se termina con ExitProcess(1), que devuelve ese código como retorno de aplicación, que puede ser interpretado desde otros programas externos o scripts.


La siguiente parte, dentro del mismo prg, define la clase c_proceso, basada en  Session, con 3 métodos: procesar, actualizarAvance y writeLog.

Procesar: Es el método principal, ya que lleva a cabo el proceso que nos interesa que sea desatendido. Genera un log al inicio y fin con algunos datos y normalmente también debería generar algún dato intermedio, que no agrego por mantener el ejemplo corto, usa la tabla con los recursos AGAIN, SHARED y ALIAS que nos permite abrirla más de una vez, tiene un método de testeo de errores sencillo, para comprobar cómo se gestionan, y luego el proceso en sí, que es el SCAN donde se va totalizando en una variable y con un retardo ficticio para que parezca un proceso pesado. Finalmente está la parte de captura del error, obtención de datos extra, generación de Log y recolección de basura, donde se cierra la tabla.


DEFINE CLASS c_proceso AS Session
  cLogFile    = 'log_proceso.txt'
  nTestError  = 0

  PROCEDURE procesar(toEx as Exception, tlRelanzarError, tnTotal)
    LOCAL lnCodError, lcMenErr

    TRY
      WITH THIS as c_Proceso OF lib_proceso.prg
        .writeLog( REPLICATE('-',80) )
        .writeLog( 'Inicio Proceso' )
        STORE 0 TO lnCodError, tnTotal
        USE T_DATOS IN 0 AGAIN SHARED ALIAS _T_DATOS
       
        IF THIS.nTestError = 1
            ERROR 'Error provocado (' + STR(.nTestError,2) + ')'
        ENDIF

        SCAN
           IF MOD(RECNO(),10)=0 THEN
              .actualizarAvance( 'Reg.', RECNO(), RECCOUNT() )
           ENDIF
           *-- El proceso real va aquí.
           tnTotal = tnTotal + valor
           INKEY(0.1,'H') && Simulo retardo proceso c/inkey()
        ENDSCAN

        .writeLog( 'Fin Proceso OK!  Total=' + STR(tnTotal) )
      ENDWITH

    CATCH TO toEx
      lnCodError = toEx.ErrorNo
      toEx.UserValue = 'algun dato importante para agregar'

      TEXT TO lcMenErr TEXTMERGE NOSHOW FLAGS 1 PRETEXT 1+2
         Error <<toEx.ErrorNo>>, <<toEx.Message>>
         Proc.<<toEx.Procedure>>, Line <<toEx.LineNo>>
         LineContents: <<toEx.LineContents>>
         Details: <<toEx.Details>>
         UserValue: <<toEx.UserValue>>
      ENDTEXT

      THIS.writeLog( lcMenErr )

      IF tlRelanzarError
        THROW
      ENDIF

    FINALLY
      USE IN (SELECT('_T_DATOS'))
    ENDTRY

    RETURN lnCodError
  ENDPROC


  PROCEDURE actualizarAvance(tcTexto, tnValor, tnTotal)
  ENDPROC


  PROCEDURE writeLog(tcTexto)
    STRTOFILE( tcTexto + CR_LF, THIS.cLogFile, 1 )
  ENDPROC


ENDDEFINE



actualizarAvance: Es un método vacío, sólo con parámetros, que sirve para ofrecer una posibilidad de monitorizar el proceso desde un programa externo, lo cuál veremos que puede ser muy útil. Observen como la llamada a actualizarAvance() está estratégicamente metida en el código para que no se ejecute por cada registro, sino cada cierta cantidad de registros. Esto es importante para evitar que cualquier monitarización externa afecte al rendimiento del proceso.

writeLog: Es la rutina centralizada para escribir el Log. Para el ejemplo opté por un log básico, sin sistema de buffering y escritura directo al disco, aunque esto en la realidad no debería ser así y es más conveniente agregarle un sistema de buffering, para evitar todas las escrituras intermedias al disco, como ya les comenté en este artículo.


Listo! Ya tenemos nuestro proceso terminado! :D



Primero vamos a probar que funciona desde la ventana de comandos de VFP:

oEx = null
nTot = 0
oo = NEWOBJECT("c_proceso", "lib_proceso.prg")
? oo.procesar(@oEx,,@nTot), oEx, nTot

0 .NULL.     5050


> Como vemos, luego de 5 segundos devuelve código de error 0, que es el retorno del método procesar, devuelve .NULL. para al objeto de errores oEx porque no hubo errores, y devuelve nTot=5050 como resultado de la sumatoria del proceso.


Ahora vamos a testear el manejo de errores:

oEx = null
nTot = 0
oo = NEWOBJECT("c_proceso", "lib_proceso.prg")
oo.nTESTERROR = 1
? oo.procesar(@oEx,,@nTot), oEx, nTot
1098 (object)   0


> En este caso devuelve el código de error 1098 como retorno del método procesar, devuelve (object) como valor de oEx y devuelve nTot = 0 porque no pudo hacer el cálculo (aunque si hubiera fallado en medio del cálculo podría haber devuelto un valor intermedio).

Finalmente comprobamos el mensaje del error devuelto:

? oEx.Message
Error provocado ( 1)


Como pudimos comprobar, el proceso funciona perfectamente y sin mostrar ninguna ventana ni mensaje, tal como debe comportarse un proceso desatendido.

Así como está, este proceso se puede utilizar dentro de un programa, o como proceso independiente en un EXE, o como proceso principal en un web-service, y obviamente también se puede llamar desde un form.

Primero vamos a probar nuestro proceso desde una ventana DOS del sistema operativo, para lo cual antes tenemos que generar un proyecto y un EXE de Fox:

BUILD PROJECT lib_proceso.pjx FROM lib_proceso.prg
BUILD EXE lib_proceso.exe FROM lib_proceso.pjx RECOMPILE



Ahora creamos el archivo test_proc.vbs y le ponemos este contenido:

Dim WSHShell, nExitCode, cEXETool
Set WSHShell = CreateObject( "WScript.Shell" )

cEXETool = Replace(WScript.ScriptFullName, WScript.ScriptName, "lib_proceso.exe")
nExitCode = WSHShell.run(cEXETool & " ""0"" ""0"" ""0"" ""0"" ", 0, True)
MsgBox "CodError = " & nExitCode , 0+4096, "Ejecución 1"

cEXETool = Replace(WScript.ScriptFullName, WScript.ScriptName, "lib_proceso.exe")
nExitCode = WSHShell.run(cEXETool & " ""0"" ""0"" ""0"" ""1"" ", 0, True)
MsgBox "CodError = " & nExitCode , 0+4096, "Ejecución 2"



Aquí estamos probando dos casos de prueba:

  • El primero ejecutará el proceso normal, que puede tardar hasta 10 segundos (esperen, no lo corten! :), y muestra un mensaje con el código de error 0 en un messagebox
  •  El segundo ejecutará el proceso simulando un error, por lo que será instantáneo y mostrará un mensaje con el código de error 1 en un messagebox

Con esto comprobamos que desde otro ejecutable o desde el Sistema Operativo, el proceso devuelve un código de error al estilo ERRORLEVEL de DOS.


Finalmente vamos a probar nuestro proceso desde un formulario de lanzamiento y monitorización, como el de esta imagen, donde pueden ver la situación inicial (el form cargado):



En esta captura está el proceso ejecutándose (va por el 60%) :



Y el fin del proceso con el total al terminar:




El código completo lo adjunto al final del artículo, pero vamos a ver la parte importante en detalle.

El botón que lanza el proceso se llama cmd_Procesar, y en su evento click tiene este código:

Local loEx As Exception, lnTot, lnRet, lcText ;
    , loProceso As c_proceso Of lib_proceso.prg

Try
    This.Enabled = .F.
    Thisform.chk_GenerarError.Enabled = .F.
    loEx = Null
    Store 0 To lnTot, lnRet
    loProceso = Newobject("c_proceso", "lib_proceso.prg")
    loProceso.nTestError = Thisform.chk_GenerarError.Value
    Bindevent( loProceso, 'ActualizarAvance', Thisform, 'ActualizarAvance' )
    lnRet = loProceso.procesar(.F.,.T.,@lnTot)
    lcText = Transform(lnTot)

Catch To loEx
    lcText    = "Error " + Transform(loEx.ErrorNo) + ', ' + loEx.Message

Finally
    Unbindevents( loProceso, 'ActualizarAvance', Thisform, 'ActualizarAvance' )
    Messagebox( lcText )
    This.Enabled = .T.
    Thisform.chk_GenerarError.Enabled = .T.
    Store Null To loProceso, loEx
Endtry



Lo más importante es el Bindevents() que se hace del método actualizarAvance() del proceso, y que se bindea a un método llamado igual en el form, donde está la implementación de la visualización, que solamente tiene este código:


LPARAMETERS tcTexto, tnValor, tnTotal

THISFORM.lbl_progreso.Caption = tcTexto + ' (' + ;

   TRANSFORM(tnValor) + '/' + TRANSFORM(tnTotal) + ')'


Con esto pueden comprobar cómo a un proceso desatendido se le puede acoplar una interfaz para controlarlo. Si quisiéramos comprobar el control de errores, solo debemos marcar el check correspondiente (puesto solo para pruebas) y elegir Procesar:




Y esta es una copia del log generado en log_proceso.txt:

--------------------------------------------------------------------------------
Inicio Proceso
Error 1098, Error provocado ( 1)
Proc.procesar, Line 39
LineContents: ERROR 'Error provocado (' + STR(.nTestError,2) + ')'
Details:
UserValue: algun dato importante para agregar
--------------------------------------------------------------------------------
Inicio Proceso
Fin Proceso OK!  Total=      5050



Pero esto deja abierta otra puerta, y es que podemos cambiar la implementación de la visualización del proceso según cómo lo queramos. Por ejemplo, podemos querer que para ver el avance del proceso se muestre una barra de progreso, que es mucho más visual y útil que un porcentaje, o incluso podemos combinar ambos para tener información más precisa:




Bueno, y con esto acabamos el ejemplo. Hemos conseguido complir todos los objetivos de un servicio desatendido comentados al inicio, tenemos la seguridad del control de errores sencillo pero eficiente, tenemos el log de todo lo ocurrido, podemos lanzar el proceso desde otros ejecutables o scripts y podemos acoplar interfaces para controlarlo.


Una cosa que muchos programadores pasan por alto, sin darse cuenta, es que en un sistema, muchos de los subprocesos que se ejecutan (un cálculo, una actualización) son realmente subprocesos desatendidos, sy solo la interfaz que los llama debería mostrar cualquier mensaje de error, de finalización o con resultados.

Vean vuestros programas y podrán darse cuenta de en muchos casos están mezclando mensajes de estado o messagebox en medio de un proceso. Como ven, hay mucho margen para mejorar eso.


Espero que les haya sido útil :)

Estos son los archivos del ejemplo


Hasta la próxima!

viernes, mayo 01, 2015

Técnicas de optimización en VFP: Tablas, archivos de texto, accesos al disco y buffering

Por: Fernando D. Bozzo

Este es un artículo de una serie que se enfocará en técnicas de programación y optimización en distintas áreas.

Un buen desarrollador asimilará estas técnicas como parte de su forma de trabajo habitual, independientemente de que haga un sistema, ún módulo, una rutina o un programa de pruebas personales, ya que le permitirá programar siempre orientado a la eficiencia, la velocidad de ejecución, la encapsulación, la reutilización y la legibilidad y mantenibilidad del código, o sea, las buenas prácticas.

Al tener en cuenta estas técnicas en cada parte del código y en cada rutina, al final lo que se logra es que el sistema completo esté más optimizado porque sus partes lo están.



Una de las operaciones más costosas a nivel de recursos en una PC es la de accesos al disco, ya que el disco es probablemente el componente más lento del sistema. Aunque los discos SSD minimizan el problema, la solución real pasa por la programación, ya que tanto los discos rígidos y sobre todo las redes, son los principales cuellos de botella.

Muchas veces un programa o un sistema comienza teniendo un único usuario o unos pocos, pero puede que con el tiempo esa condición cambie, y que comiencen a haber más usuarios, incluso muchos usuarios, y es aquí cuando comienzan a verse realmente los problemas de las decisiones de diseño tomadas y donde ya es tarde para cambiarlo por el esfuerzo que puede requerir, implicando a veces un rediseño, por eso cada línea de código cuenta.



Optimizaciones en el uso de archivos y accesos a disco


Caso 1: Los LOGs


Normalmente, cuando se quiere hacer un log al disco de alguna información importante para nosotros o para su posterior explotación o análisis, se suele usar STRTOFILE() por comodidad o por falta de tiempo. He aquí algunos ejemplos típicos:


1.a) Ejemplo de un LOG dentro de un bucle que puede tener cualquier cantidad de registros:

SCAN FOR <condición>
  ...
  IF <condicion_que_requiere_loguear>
     STRTOFILE( 'alguna información importante', 'LOG.txt', 1 )
  ENDIF
ENDSCAN


1.b) Ejemplo de un LOG en un evento Timer que podría ejecutarse varias veces por segundo:

PROCEDURE TIMER
   ...
  IF <condicion_que_requiere_loguear>
     STRTOFILE( 'alguna información importante', 'LOG.txt', 1 )
  ENDIF
ENDPROC


1.c) Ejemplo de un LOG en un método de cálculo reutilizable, que podría llamarse cientos de veces desde distintas partes de un sistema:

PROCEDURE Sumar_Porcentaje
  ...
  IF <condicion_que_requiere_loguear>
     STRTOFILE( 'alguna información importante', 'LOG.txt', 1 )
  ENDIF
ENDPROC


1.d) Ejemplo de un LOG en un método de proceso largo, donde se realizan diversas operaciones y por cada una se va actualizando el LOG:

PROCEDURE Proceso_Largo
  <operación-1> 
  STRTOFILE( 'alguna información importante', 'LOG.txt', 1 )
  <operación-2> 
  STRTOFILE( 'alguna información importante', 'LOG.txt', 1 )
  ...
  <operación-n>
  ...
ENDPROC

En todos los casos, STRTOFILE() escribirá al disco para ir añadiendo información al LOG. Para unas pocas repeticiones esto puede no ser un problema, pero siempre hay que asumir un caso de uso desfavorable, en este caso intensivo.

Veamos qué ocurre cuando la repetición es de 100 iteraciones:
  • Para 1 usuario implicará 100 accesos a disco
  • Para 5 usuarios implicará 500 accesos a disco
  • Para 10 usuarios implicará 1000 accesos a disco

Puede verse que el impacto se multiplica rápidamente a medida que crece la cantidad de usuarios, lo que puede causar que el sistema de archivos se vaya poniendo cada vez más lento por la metralla de escrituras, pudiendo llegar incluso a no dar abasto en responder las peticiones y provocar el colapso del sistema.

Otro error común al escribir LOGs es que se suele elegir un nombre (ej: LOG.txt) y se olvida el hecho de que esto puede ser usado por varios usuarios concurrentes, lo que puede provocar la de contención de recursos porque dos ó más personas intentan usar el mismo archivo con el mismo nombre; también puede provocar errores, en el caso de que el LOG se use en exclusiva (con FOPEN/FWRITE) o también puede provocar pérdida de información de partes del LOG, ya que mientras un usuario está escribiendo en el LOG, otro no puede hacerlo y STRTOFILE() simplemente no escribe y tampoco generará un error por ello (STRTOFILE solo devuelve la cantidad de bytes escritos, 0 en caso de no poder escribir, pero nadie suele verificar esto)



Soluciones


Para resolver el problema de los usuarios concurrentes, se puede usar un LOG distinto por usuario, que puede tener una numenclatura parecida a "LOG_UsuarioDeRed.txt", o una más completa como "LOG_UsuarioDeRed_NombreMaquina.txt". Si se quiere un historial de LOGs diarios, se podría agregar la fecha al nombre del LOG. Como se ve, variantes hay para todos los gustos.

Para minimizar el impacto de estas escrituras se suele usar la estrategia del buffering (o caché), que implica ir guardando la información que se quiere escribir en una variable de memoria o una propiedad y cada tanto realizar la escritura de todo lo guardado. En este caso hay varias alternativas, que van desde un objeto LOG que autogestione la cantidad de texto que puede guardar para hacer escrituras automáticas al llegar a cierto tamaño acumulado, o a algo más sencillo y práctico como hacer dos métodos, uno de escritura en buffer y uno de flush al disco, que es la técnica que usé en FoxBin2Prg y que muestro a continuación, simplificado y adaptado para el ejemplo:


DEFINE CLASS C_LOG
  #DEFINE CR_LF CHR(13)+CHR(10)
  c_TextoLog = ''
  c_LogFile  = ''

  PROCEDURE Init
      c_LogFile = ADDBS( SYS(2023) ) + 'LOG_' ;
          + CHRTRAN( SYS(0), ' ', '_' ) + '.TXT'
  ENDPROC
 
  PROCEDURE Destroy
      THIS.writeLog_Flush()
  ENDPROC
 
  PROCEDURE writeLog
    LPARAMETERS tcText

    TRY
      WITH THIS
        .c_TextoLog = .c_TextoLog + EVL(tcText,'') + CR_LF
      ENDWITH
    CATCH && En este caso no me interesa reportar errores
    ENDTRY
  ENDPROC

  PROCEDURE writeLog_Flush

    WITH THIS
      IF NOT EMPTY(.c_TextLog)
        STRTOFILE( .c_TextLog + CR_LF, .c_LogFile, 1 )
      ENDIF
      .c_TextLog    = ''
    ENDWITH
  ENDPROC 


ENDDEFINE


En el ejemplo, en el Init() se define en nombre del LOG, luego se usa el método writeLog() para escribir el texto que se quiera en la propiedad c_TextoLog y a la que se agrega un fin de línea, y finalmente el método writeLog_Flush() que se encarga de escribir al disco todo el texto acumulado y de vaciar la propiedad que lo acumula. Cuando se descarga la clase, también se escribe automáticamente al disco lo que quede por escribir.

Este es un método muy simple y efectivo que permite sustituir los STRTOFILE() de los ejemplos anteriores por writeLog() y que solo requiere ubicar las llamadas a writeLog_Flush() fuera de los bucles de repetición o de las rutinas de uso frecuente, pero siempre teniendo en cuenta que en puntos estratégicos debe ser ejecutado, para evitar que el LOG se acumule indefinidamente y cause problemas de memoria.

Usando esta técnica, las escrituras al disco o por la red se pueden minimizar de forma muy notoria, y lograr que el sistema siga siendo responsivo.



Caso 2: Actualización de TABLAS


En Fox, una de las cosas que más se usa son las tablas y los cursores, tanto para guardar datos como para guardar información temporal de proceso, ya que Fox está optimizado para eso a tal punto que manejar un cursor de un millón de registros es más rápido que manejar un array de la misma cantidad de filas.

Pero esta orientación a datos muchas veces no es bien implementada, y es muy común encontrarse con código como este:


DO WHILE <condición>
  ...
  REPLACE campo1 WITH valor1
  REPLACE campo2 WITH valor2
  REPLACE campo3 WITH valor3
  REPLACE campo4 WITH valor4
  REPLACE...
  ...
ENDDO


Al igual que en el Caso 1, si este bucle se repite en 100 iteraciones y usando solo 5 REPLACE:
  • Para 1 usuario implicará 500 accesos a disco
  • Para 5 usuarios implicará 2500 accesos a disco
  • Para 10 usuarios implicará 5000 accesos a disco

Puede notarse que en este caso todavía es peor que en el anterior, ya que muchos están acostumbrados a poner varios REPLACE en sucesión, por motivos como que "es más fácil para copiar" o simplemente porque les gusta verlos separados...



Esta es otra variante bastante común que se puede encontrar, donde es necesario hacer REPLACE solo bajo ciertas condiciones y muchos lo implementan así:

SCAN FOR <condición>
  ...
  IF <cond.1>
    REPLACE campo1 WITH valor1
  ENDIF
  IF <cond.2>
    REPLACE campo2 WITH valor2
  ENDIF
  IF <cond.3>
    REPLACE campo3 WITH valor3
  ENDIF
  IF <cond.4>
    REPLACE campo4 WITH valor4
  ENDIF
  IF ...
    REPLACE...
  ...
ENDSCAN


La única diferencia con el caso anterior, es que en cada iteración la cantidad de reemplazos no es fija porque depende de condiciones, pero como siempre, hay que ubicarse en el peor caso donde todas las condiciones puedan ser verdaderas, por lo que en este caso se puede llegar a los mismos 5 REPLACE del ejemplo anterior y a las mismas estadísticas de accesos a disco por usuario.


La realidad es que tanto esta como la otra son malas prácticas y deben evitarse a toda costa, porque penalizan mucho el rendimiento, y más en una red.



Soluciones


Básicamente las soluciones pasan por realizar estos reemplazos en una sola operación, y estas técnicas sirven para cubrir ambos casos con mucha facilidad, como puede verse en los siguientes ejemplos.


Ejemplo 1: Usando un objeto de registro


DO WHILE <condición>
  ...
  SCATTER NAME loReg && o SCATTER FIELDS para más precisión
  loReg.campo1 = valor1
  loReg.campo2 = valor2
  loReg.campo3 = valor3
  loReg.campo4 = valor4
  loReg.campoN...
  GATHER NAME loReg && Un único reemplazo
  ...
ENDDO


Ejemplo 2: Usando variables

SCAN FOR <condición>
  ...
  lc_campo1 = valor1
  lc_campo2 = valor2
  lc_campo3 = valor3
  lc_campo4 = valor4
  lc_campoN...
  REPLACE campo1 WITH lc_campo1 ;
    , campo2 WITH lc_campo2 ;
    , campo3 WITH lc_campo3 ;
    , campo4 WITH lc_campo4 ;
    , campoN WITH lc_campoN && Un único REPLACE para todos
  ...
ENDSCAN


Ejemplo 3: Usando un array

SCAN FOR <condición>
  ...
  SCATTER TO ARRAY laReg && o SCATTER FIELDS para más precisión
  laReg(1) = valor1
  laReg(2) = valor2
  laReg(3) = valor3
  laReg(4) = valor4
  laReg(N)...
  GATHER FROM laReg && Un único reemplazo
  ...
ENDSCAN


Y para reemplazos condicionados se puede usar el mismo código de los ejemplos, solo que condicionando las asignaciones de las variables o propiedades.



El caso anterior fue para reemplazos de datos existentes, pero para registros nuevos es lo mismo, con la salvedad de que como primera opción más recomendable se agrega el Insert-SQL:

Ejemplo 1: Usando Insert-SQL para un registro

SCAN FOR <condición>
  ...
  SCATTER BLANK NAME loReg
  loReg.campo1 = valor1
  loReg.campo2 = valor2
  loReg.campo3 = valor3
  loReg.campo4 = valor4
  loReg.campoN...
  INSERT INTO <tabla> FROM NAME loReg
  ...
ENDSCAN


Ejemplo 2: Usando Insert-SQL para varios registros

SCAN FOR <condición>
  ...
  DIMENSION laReg(3,5)
  laReg(1,1) = valor1
  laReg(1,2) = valor2
  laReg(1,3) = valor3
  laReg(1,4) = valor4
  laReg(1,5...
  ...
  INSERT INTO <tabla> FROM laReg
  ...
ENDSCAN


Otra variante es usar un cursor con la misma estructura que la tabla, realizar los Inserts en el cursor y luego volcarlo en la tabla.

Como se puede ver opciones hay muchas, y cada una puede ser más óptima que la otra dependiendo de nuestras necesidades o de cómo queramos implementarlo, pero para el buffering en memoria podemos usar tanto variables como cursores, pudendo incluso usar buffering de tablas o registros mediante CursorSetprop().

Nota: En el caso de usar cursores o buffering de tablas o registros, recordar cada tanto usar la función SYS(1104) para liberar los buffers de memoria. Como ejemplo, cada 100 registros reemplazados se podría forzar la limpieza de los buffers para liberar memoria.




Caso 3: Escritura de grandes cantidades de texto (>1 MB y <= 2 GB)


Hay situaciones en las que tenemos un proceso que requiere ir generando información al disco en formato texto. La diferencia con un LOG, como vimos al inicio, es que un LOG puede desactivarse o condicionarse, mientras que en este caso hablamos de un proceso que sí o sí debe escribir al disco para generar un archivo de texto en múltiples pasos o subprocesos. Un ejemplo de este tipo de proceso puede ser un parser o un conversor, donde se va interpretando el origen de datos (o un archivo origen) y a la vez se va generando la información de salida a un archivo de texto.

Si bien la primera reacción puede ser la de usar la técnica del buffering del Caso 1 mediante el uso de una variable o una propiedad acumuladora, es necesario saber que para textos superiores a 1 MB FoxPro se empieza a poner lento, principalmente por la acumulación en una variable que va creciendo y que además va consumiendo cada vez más memoria.

En estos casos, lo más óptimo es usar funciones de bajo nivel como FOPEN/FWRITE con buffering, lo que da una velocidad que como mínimo duplica al buffering por variable o propiedad acumulada.

Aunque pueda parecer contradictorio decir que una función de manejo de archivos de bajo nivel sea más rápida en estos casos que una variable, la explicación está en que no solo Fox no se maneja bien con variables con mucho contenido que se actualiza constantemente, sino que además las funciones de manejo de archivos a bajo nivel con uso de buffering están optimizadas para justamente para no escribir todo el tiempo al disco, sino que implementan su propio esquema de buffering.




Caso 4: Generación de texto con TEXT/ENDTEXT


No se puede hablar de generación de texto sin hablar de TEXT/ENDTEXT, que es uno de los comandos más potentes y versátiles de FoxPro desde los inicios, pero primero veamos un ejemplo de la salida que queremos conseguir:




Fecha: 01/05/2015                    Fernando Caimo

Número de viajes: 30
Kilómetros hechos: 275

------------------------------------------------------------



Así es cómo se puede generar este texto usando variables y contenido dinámico:


cTexto = ''
cTexto = cTexto + chr(13)+chr(10)
cTexto = cTexto + 'Fecha: ' + dtoc(date()) + space(20) ;
  + cApellidoYNombre + chr(13)+chr(10)
cTexto = cTexto + chr(13)+chr(10)
cTexto = cTexto + 'Número de viajes:  ' + TRANSFORM(nViajes) ;
  + chr(13)+chr(10)
cTexto = cTexto + 'Kilómetros hechos: ' + TRANSFORM(nKM) ;
  + chr(13)+chr(10)
cTexto = cTexto + chr(13)+chr(10)
cTexto = cTexto + replicate('-', 60)



Y esta es la forma en que se hace con TEXT/ENDTEXT:

TEXT TO cTexto ADDITIVE TEXTMERGE NOSHOW FLAGS 1+2 PRETEXT 1+2

Fecha: <<date()>>                    <<cApellidoYNombre>>

Número de viajes: <<nViajes>>
Kilómetros hechos: <<nKM>>

------------------------------------------------------------

ENDTEXT


....no hay contraste! TEXT/ENDTEXT es tan increíblemente versátil y claro para generar este tipo de documentos que deja en vergüenza al resto de métodos disponibles.

Pero tanta potencia tiene sus casos de uso y su coste, por lo que tampoco es cuestión de usarlo para cada línea de texto que se quiera generar, y es que para líneas individuales, y más si se usa de la siguiente forma, no es recomendable:

FOR X=1 TO loProcedure._ProcLine_Count
  TEXT TO lcMemo ADDITIVE TEXTMERGE NOSHOW FLAGS 1+2 PRETEXT 1+2
    <<loProcedure._ProcLines(X)>>
  ENDTEXT
ENDFOR

STRTOFILE(lcMemo, 'salida.txt', 1)



Como puede verse, aunque la parte de escritura con STRTOFILE está optimizada porque está fuera del bucle, lo que de por sí impactará poco en accesos a disco, el TEXT/ENDTEXT está en un bucle de repetición cuya iteración puede ser alta, y el problema con este caso en particular es que por cada línea que se genera y agrega a lcMemo, se requiere inicializar una estructura de parseo, parsear cada uno de los parámetros indicados y luego procesar el texto entre TEXT y ENDTEXT usando los parámetros indicados. Y todo esto para una línea en un bucle es demasiado coste para la CPU.

En su lugar, y para este caso particular, lo más conveniente y óptimo es usar una variable:

FOR X=1 TO loProcedure._ProcLine_Count
  lcMemo = lcMemo + chr(13)+chr(10) + loProcedure._ProcLines(X)
ENDFOR

STRTOFILE(lcMemo, 'salida.txt', 1)




Resumen


El objetivo siempre debe ser hacer el código más óptimo para cada situación, y en el caso de los archivos siempre se trata de minimizar los accesos a disco usando técnicas de buffering.

Lo que se logra con estas técnicas, específicamente, es:
  • Menor cantidad de accesos a disco (o red)
  • Menor uso de recursos del sistema
  • Disminución de posibilidades de contención
  • Mayor velocidad de proceso
  • Alargamiento de la vida útil de los discos, tanto rígidos como SSD

Puede que hayan quedado fuera algunas cosas, pero quería hacer un resumen con algunas de las más importantes a tener en cuenta.


Hasta la próxima! :D



martes, abril 21, 2015

Visual FoxPro - Selección de artículos y notas sobre problemas y troubleshoting para tener a mano

[2015/04/21]

Esta es una selección de artículos y notas que iré actualizando con el tiempo, ya que a medida que van saliendo nuevas versiones de Windows, aparecen nuevos problemas para las aplicaciones que usan file-sharing como FoxPro, Acces y varias otras. Las más nuevas las iré agregando arriba.


(Muy buena explicación técnica sobre cacheo en samba)
Re: Network Slow Query / Christof Wollenhaupt / 2014.12.18

http://leafe.com/archives/msg/497230


Data corruption when multiple users perform read and write operations to a shared file in the SMB2 environment
https://support.microsoft.com/en-us/kb/2028965

 .

domingo, diciembre 14, 2014

Whitepaper: Cacheo de lectura y bloqueo oportunista en redes Microsoft Windows

Traducción del artículo "Opportunistic Locking and Read Caching on Microsoft Windows Networks", de Dennis Piccioni. Última edición: septiembre 6, 2013


N.del T.: Aunque no se menciona a Visual FoxPro, se menciona un sistema de base de datos similar (Visual DataFlex), por lo que aplican cada uno de los problemas y soluciones.


Nota 2: El cacheo es un almacenamiento temporal en memoria caché (RAM).



Resumen


Las redes Windows configuradas inapropiadamente puede conducir a la corrupción en los datos en cualquier sistema de archivos de base de datos, incluyendo la base de datos (DataFlex) embebida. Dos comportamientos de red de Windows, el bloqueo oportunista (en servidores Windows) y el cacheo de lectura (en clientes Windows) son fuentes de posible corrupción.

Se proporciona este documento para los clientes de Data Access Worldwide (DAW) para discutir estos comportamientos, sus efectos y qué se puede hacer para reducir al mínimo las posibilidades de corrupción de datos en las redes de Windows cuando se ejecuta Visual DataFlex (VDF) y/o aplicaciones DataFlex, y centralizar la información en un solo lugar.
 
La información contenida en este documento es una compilación de la última información disponible sobre estos temas desde Microsoft, nuestros propios informes de pruebas en la empresa y los clientes. Estamos tratando de combinar la escasa información proporcionada mi Microsoft sobre estos temas en un solo lugar. Por favor revisar este documento de vez en cuando para comprobar la información actualizada. La fecha corregida por último en la parte superior del papel reflejará cuando se hicieron las últimas ediciones.
 
La información contenida en este documento sólo se ocupa de los sistemas operativos que actualmente apoyamos. Usted puede ver información acerca de productos y entornos admitidos en la lista de productos soportados por Data Access Worldwide .

 

Contenido

  • ¿Qué es el bloqueo oportunista?
  • ¿Qué es el Cacheo de Lectura?
  • ¿Qué son SMB2 y SMB3?
  • Recomendaciones de Data Access Worldwide
  • ¿Qué sistemas operativos están afectados?
  • Qué Entornos no están afectados?
  • Haciendo Cambios en el Registro de Windows
  • Desactivando Oplocks en PC Clientes Windows
  • Desactivando Oplocks en Servidores Windows
  • Desactivando Oplocks en SMB2 y SMB3
  • ¿Las prácticas de codificación afectan a estas cuestiones?
  • Corrupción de datos Persistente
  • Términos
  • Recursos
 

¿Qué es el bloqueo oportunista?


El bloqueo oportunista (oplocks) es un mecanismo específico para Windows para bases de datos cliente/servidor para permitir que varios procesos bloqueen el mismo archivo mientras permite el almacenamiento de datos local (cliente) en caché para mejorar el rendimiento sobre redes Windows.  

Desafortunadamente, la configuración predeterminada del mecanismo oplocks que mejora el rendimiento de un tipo de base de datos (cliente/servidor) también presenta problemas de integridad de datos para otros tipos de bases de datos (sistema de archivos/ISAM ).

La documentación de Microsoft indica que "un bloqueo oportunista (también llamado oplock) es un bloqueo puesto por un cliente en un archivo que reside en un servidor. En la mayoría de los casos, un cliente solicita un oplock así puede almacenar en caché los datos localmente, lo que reduce el tráfico de red y la mejora de tiempo de respuesta aparente. Los Oplocks son utilizados por los redirectores de red en los clientes con servidores remotos, así como por las aplicaciones de cliente en servidores locales" y "los Oplocks son solicitudes del cliente al servidor. Desde el punto de vista del cliente, son oportunistas. En otras palabras, el servidor garantiza dichos bloqueos cuando otros factores hacen esos bloqueos posibles".

Puedes leer más sobre oplocks en la documentación de Microsoft. Por favor vea la Recursos sección para obtener más información.


 

¿Qué es el Cacheo de Lectura?


El cacheo de lectura, a veces conocido como lectura anticipada en caché, es una característica de bloqueos oportunistas. Es una técnica utilizada para acelerar el acceso de red a los archivos de datos. Se trata de cachear datos en los clientes en lugar de en los servidores cuando sea posible.

El efecto del cacheo local es que permite que múltiples operaciones de escritura en la misma región de un archivo que se combinan en una sola operación de escritura a través de la red. El cacheo local reduce el tráfico de red, ya que los datos se escriben una vez. Dicho cacheo mejora el tiempo de respuesta aparente de aplicaciones porque las aplicaciones no esperan a que los datos se envíen a través de la red al servidor.
 
Los problemas con el cacheo de lectura por lo general se producen si algo inesperado sucede, como un cuelgue de la estación de trabajo, donde los datos no se flushean debidamente desde la estación de trabajo, lo que puede llevar a corrupción en los datos.

La documentación de Microsoft señala que «En condiciones extremas, algunas aplicaciones de bases de datos multiusuario que utilizan un almacén de datos común a través de una conexión de red en un servidor de archivos, pueden experimentar problemas de integridad transaccional o la corrupción de los archivos y/o índices de base de datos almacenados en el servidor. Normalmente, esto se aplica a las aplicaciones de bases de datos multiusuario "estilo ISAM" u "orientadas a registros", y no a sistemas relacionales cliente/servidor como SQL Server» y que «un peligro del cacheo local es que los datos escritos sólo tienen tanta integridad como el propio cliente, en tanto que los datos se cachean en el cliente. En general, los datos cacheados localmente se deben flushear al servidor lo antes posible.»
 
Puedes leer más sobre el almacenamiento en caché leer en la documentación de Microsoft.


 

¿Qué son SMB2 y SMB3?


SMB2 y SMB3 son la segunda y tercera generación, respectivamente, de las comunicaciones de bloque de mensajes de servidor (SMB) en redes Windows.

SMB2 se introdujo en Windows Vista, 7 y Windows Server 2008 para permitir una comunicación más rápida entre los equipos que ejecutan Windows Vista, 7 y Windows Server 2008. SMB3 se introdujo en Windows 8 y Server 2012.

Las versiones anteriores de Windows (Windows XP, Server 2003 y anteriores) utilizaban SMB1, también llamado SMB "tradicional". SMB1 aún se soporta en las versiones actuales de Windows (Vista, 7, 8, Server 2008, Server 2012) para la compatibilidad con versiones anteriores.


 

Recomendaciones de Data Access Worldwide


La base de datos embebida (DataFlex) es una base de datos ISAM y por lo tanto susceptibles a los efectos de la configuración predeterminada de Windows oplocks. Usar la base de datos embebida en las redes de Windows sin desactivar oplocks no se recomienda ni se soporta y tiene una alta probabilidad de corrupción de datos.
 
La mejor integridad de los datos, seguridad y rendimiento está disponible mediante el uso de una base de datos cliente/servidor, como IBM DB2, Microsoft SQL Server o Pervasive.SQL con sus aplicaciones de Visual DataFlex y DataFlex. Data Access Worldwide tiene drivers directos (Kits de Conectividad) disponibles para IBM DB2 , Microsoft SQL Server y Pervasive.SQL , así como un kit de conectividad ODBC para el acceso a las bases de datos compatibles con ODBC. Todos estos controladores se cargan en tiempo de ejecución y no requieren cambios de codificación para utilizar con las aplicaciones existentes VDF, DataFlex o aplicaciones web del servidor.
 
Se puede lograr una operación confiable de base de datos en redes de Windows utilizando la base de datos embebida, siempre que la red está configurada correctamente. Puede utilizar la información en este documento para configurar los parámetros de oplocks de su red de Windows. Una desventaja de utilizar este método son los problemas de mantenimiento: se debe asegurar continuamente de que todos y cada uno de los servidores y clientes que utilicen una aplicación que acceda a la base de datos embebida, tengan desactivado oplocks y que siempre se mantengan en ese estado.
 
Un método para asegurar que oplocks está desactivado en los PC cliente es agregar código para aplicaciones que compruebe esos ajustes al inicio de la aplicación. La gente de VDF-Guidance.com ha creado un proyecto de código abierto llamado regcheck para este propósito.
 
Desactivar Oplocks puede tener un impacto en el rendimiento de las redes de Windows.
 
Los Oplocks no se aplican a las bases de datos cliente-servidor. DAW no hace ninguna recomendación específica sobre oplocks si utiliza una base de datos cliente-servidor y no hay tablas de base embebidos.
 
Este artículo le dirá cómo deshabilitar los bloqueos oportunistas, pero debido a las razones expuestas anteriormente Data Access Worldwide recomienda el uso de una base de datos cliente-servidor para las aplicaciones multi-usuario DataFlex en redes Windows.


 

¿Qué sistemas operativos están afectados?


Todos los equipos que ejecutan sistemas operativos Windows que hospeden o accesen tablas de bases de datos embebidas accedidas por otras PCs con Windows necesita tener oplocks dasactivado con el fin de minimizar las posibilidades de corrupción de base de datos.
 
Los Oplocks se pueden desactivar en uno (o ambos) de los siguientes:
  • el lado del cliente (un PC con Windows que tiene acceso a una tabla de base de datos embebida alojado en otro PC)
  • el lado del servidor (un PC con Windows que aloja una mesa de base de datos embebida que se accede desde otro PC)

La lista del sistema operativo Windows que actualmente soportamos para nuestros productos incluye Windows 7, Windows 8, Windows 10, Windows Server 2008 y Windows Server 2012.


 

¿Qué Entornos no están afectados?


Hay algunos entornos y escenarios que soportamos que no pueden verse afectados por bloqueos oportunistas, incluso si se utiliza la base de datos embebida:
 
  • Acceso de base de datos local
En general, cada vez que se accede a una tabla de base de datos embebida en el mismo PC donde se encuentra ubicada, los oplocks no se aplican.
  • Windows Terminal Services y Citrix
En condiciones de uso normal para estos entornos, los usuarios inician sesión en un servidor de Windows y ejecutan aplicaciones de forma local en ese servidor. Si, sin embargo, una base de datos embebida se encuentra en otro servidor del que ejecuta WTS/Citrix, los oplocks entre el servidor WTS/Citrix y el servidor de base de datos deben ser desactivados.
  • Aplicaciones de Servidor Web / Servicios Web
En las aplicaciones web los usuarios acceden a un navegador web que solicita datos desde una aplicación Web y/o los datos se solicitan a través de un servicio web. En ambos casos, la aplicación web en un servidor web tiene acceso a la base de datos, el cliente no lo tiene. Si los datos se encuentran en el mismo servidor, oplocks no entran en juego. Si, sin embargo, una base de datos embebida se encuentra en otro servidor del que ejecuta la aplicación web, los oplocks entre el servidor web y el servidor de base de datos deben ser desactivados.



Haciendo Cambios en el Registro de Windows


Los temas siguientes abordan la edición de cambios en el Registro de Windows.
 
Precaución: La siguiente advertencia aparece en cada artículo de Microsoft que discute la edición del Registro de Windows:
 
ADVERTENCIA: Puede editar el registro mediante el Editor del Registro (Regedit.exe o Regedt32.exe). Si utiliza el Editor del Registro incorrectamente, puede provocar problemas graves que conlleven la reinstalación del sistema operativo. Microsoft no garantiza que los problemas que le causan mediante el Editor del Registro de forma incorrecta se pueden resolver. Utilice el Editor del Registro bajo su propio riesgo.

Si cambia alguno de los valores del registro discutidos a continuación, tendrá que reiniciar el PC en el que se ha cambiado el valor para asegurar que la nueva configuración entre en efecto.
 
Los cambios en el registro se listan en el formato MainRegistryKey \ subclave \ subclave RegistryValue = RequiredValue
 
donde:
  • MainRegistryKey es una de las principales claves del Registro de Windows (por ejemplo HKey_Local_Machine)
  • Subclave es cualquier subclave de una clave principal Registro
  • RegistryValue es un valor del Registro para cambiar o añadir en la clave de registro especificada
  • RequiredValue es la RegistryValue valor debe ajustarse para causar el efecto descrito

Si no existen las subclaves o valores que se describen en el Registro, que tendrá que agregarlos. Por favor, revise cuidadosamente antes de hacerlo.


 

Desactivando Oplocks en PC Clientes Windows


Para deshabilitar los bloqueos oportunistas en un PC cliente de Windows (un PC con Windows que tiene acceso a una tabla de base de datos embebida alojada en otro PC), cambiar o añadir los siguientes valores del registro:
 
  • HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services\MRxSmb\Parameters OplocksDisabled = 1



Desactivando Oplocks en Servidores Windows


Para deshabilitar los bloqueos oportunistas en un servidor de Windows (un PC con Windows que hospeda una tabla base de datos embebida que se accede desde otro PC), cambiar o añadir los siguientes valores del registro:
 
  • HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\LanmanServer\Parameters EnableOplocks = 0



Desactivando Oplocks en SMB2 y SMB3


Oplocks no se puede apagar para SMB2 y SMB3. Puede deshabilitar SMB2 y SMB3 en sí mismos, cómo hacerlo está documentado por Microsoft en el artículo 2696547 de Knowledge Base .
 
De acuerdo con ese artículo, SMB2 y SMB3 se pueden desactivar en los sistemas operativos Windows compatibles con estos.
 
Para deshabilitar SMB2 y SMB3 en un Windows Vista, 7, 8, Server 2008 o Server 2012 PC que alberga tablas de bases de datos embebidas, cambie o agregue el siguiente valor del Registro:
 
  • HKEY_LOCAL_MACHINE \ SYSTEM \ CurrentControlSet \ Services \ LanmanServer \ Parameters SMB2 = 0

Una vez SMB2 y SMB3 están desactivados, SMB1 debe volver a habilitarse para ser utilizado de nuevo y los métodos descritos anteriormente se aplican para desactivar oplocks para SMB1.
 
Para volver a habilitar SMB1 en un Windows Vista, 7, 8, Server 2008 o Server 2012 PC que alberga tablas de bases de datos embebidas, cambio o agregue el siguiente valor del Registro:
 
  • HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\LanmanServer\Parameters SMB1 = 1



¿Las prácticas de codificación afectan a estas cuestiones?

 
  • Si su código de aplicación utiliza DataDictionaries y/o Data_Sets, no debe haber problemas de integridad de datos después de que los bloqueos oportunistas se han desactivado.
  • Los clientes han informado de que con el código de aplicación que no utilice diccionarios de datos y/o conjuntos de datos (por ejemplo, en un bucle de Búsqueda que utiliza la memoria intermedia para encontrar registros), los datos en los registros nuevos o editados desde que los datos fueron accedidos por primera vez, seguirán sin recuperarse correctamente, incluso con oplocks deshabilitadas. La solución alternativa para esta condición es hacer la búsqueda en un estado de bloqueo o la emisión de un comando que vuelva a leer después de que se encontró cada registro (recuerde que debe emitir un comando de desbloqueo después de una relectura, ya que la relectura realiza un bloqueo como parte de su funcionalidad). Vamos a publicar cualquier información adicional que obtengaos acerca de cómo solucionar este problema en el sistema operativo de Microsoft en cuanto esté disponible.
  • Hemos tratado de utilizar la función de API Win32 FlushFileBuffers de Windows que Microsoft recomienda en su documentación del runtime de DataFlex/ Visual DataFlex cuando el atributo DF_HIGH_DATA_INTEGRITY estaba activado. Sin embargo, el rendimiento de aplicaciones se ha degradado hasta el punto de que era prácticamente inutilizable al hacerlo, porque esta función API de Windows es una llamada muy genérica que flushea todos los buffers en un PC cliente en lugar de sólo los utilizados por una aplicación.


 

Corrupción de datos Persistente


Si ha aplicado a todos los valores que se tratan en este trabajo, pero los problemas de corrupción y otros síntomas persisten, he aquí algo de información adicional:
 
  • Tenemos informes creíbles de los desarrolladores de que hardware de red defectuoso, como una sola tarjeta de red defectuosa, puede causar síntomas similares a la corrupción de datos.
  • Si usted ve la corrupción de datos persistentes incluso después de repetidas reindexaciones, es posible que tenga que reconstruir las tablas en cuestión. Esto implica la creación de una nueva tabla con la misma definición que latabla a ser reconstruida y transferir los datos de la tabla antigua a la nueva. Hay varios métodos conocidos para hacer esto que se pueden encontrar en nuestra base de conocimientos .

 

Términos

 
  • ISAM El Método de Indexado de Acceso Secuencial es un sistema de gestión de archivos desarrollado en IBM que permite acceder a los registros de forma secuencial (en el orden en que se ingresaron) o aleatoria (con un índice).
  • SMB El Protocolo de Bloque de Mensajes de Servidor (SMB) es un protocolo de uso compartido de archivos de red, y como se aplica en Microsoft Windows se conoce como protocolo Microsoft SMB. Si desea conocer más acerca de SMB, consulte la documentación de Microsoft.
 
Es posible que desee comprobar si hay una versión actualizada de este documento de vez en cuando. Muchos de nuestros whitepapers se actualizan cuando cambia la información. Para estos documentos, la fecha de Última edición está siempre en la parte superior del documento.


 

Recursos

 
  • Regcheck Una de las mejores maneras de asegurar que los oplocks están desactivadas en los PC cliente es añadir código para aplicaciones que comprueba los ajustes en el arranque. La gente de VDF-Guidance.com han creado un proyecto de código abierto llamado regcheck para este propósito. Tenga en cuenta que el código de este proyecto no se verifica o mantenida por DAW.
  • Soporte de Data Access Worldwide
    Visita la página de inicio de soporte DAW para obtener información sobre todas nuestras ofertas de soporte, incluyendo la lista de productos admitidos, formularios de informe de errores y ofertas de soporte libres, como la base de conocimientos,Whitepapers y foros de apoyo entre compañeros.