PDA

Ver la Versión Completa : Tabla en memoria y cada tanto actualizar DB


jars
23-02-2016, 16:39:36
Hola amigos.
Necesito trabajar con una tabla en memoria que incialmente levante registros de una tabla Firebird, haga actualizaciones, insert y delete y cada tanto actualizar esos cambios en Firebird. Alguna ayuda por fabor.
Trabajo con Delphi 7 y Firebird 2.5
Gracias.

Casimiro Notevi
23-02-2016, 16:47:08
Alguna ayuda por fabor. Y exactamente ¿el problema o la duda cuál es?

jars
23-02-2016, 16:49:41
El problema es que nunca utilize tablas en memoria que pueda modificar y luego cada tanto actualize el motor.
Se que se puede hacer con ClientDataset pero no tengo idea de como hacerlo. Si tienen algun ejemplo me ayudaria bastante.

AgustinOrtu
23-02-2016, 17:01:22
Busca información sobre ClientDataSet, en concreto sobre Cached Updates

Casimiro Notevi
23-02-2016, 17:49:00
Mira esto (http://www.intitec.com/varios/La_potencia_de_los_TClientDataSet.pdf).

jars
23-02-2016, 18:03:54
Gracias Casimiro, creo que con esto me alcanza.

ecfisa
23-02-2016, 18:04:13
Hola jars.

¿ Y con que componentes te estas conectando ?

Saludos :)

jars
23-02-2016, 18:23:25
Estoy usando SQLDirect.
Acabo de leer el doc "La potencia de los ClientDataSet" pero al final dice que para un alta masiva no es conveniente.
Mi idea es actualizar en memoria alrededor de 1000 registros y cada tanto bajarlos a FireBird por el hecho que si se corta la luz, es minima la informacion que se pierde. Me conviene usar este esquema?

ElKurgan
23-02-2016, 19:26:26
Hombre, no se si se puede considerar 1.000 registros como una carga masiva.

Por propia experiencia, en algunas aplicaciones que hemos utilizado en el curro las actualizaciones de ClientDataset.ApplyUpdates se han ralentizado mucho a partir de 15.000 o 20.000 registros, y eso dependiendo también del equipo y la conexión de red.

Saludos

jars
24-02-2016, 14:17:39
Me estoy encontrando con el problema que cada vez que actualizo en memoria el ClientDataset (sin hacer el Apply) me va duplicando la memoria consumida y luego al hacer el ClientDataSet.ApplyUpdates(0) ademas de tardar una eternidad no devuelve toda la memoria y asi sigue creciendo hasta que Falla.

El ClientDataSet esta enlazado con un DataSetProvider y este con un TSDQuery (SQLDirect)
Este es el código:


var
// TPosData es un registro de prueba
Pos: array [0..299] of TPosData;

begin
for i := 0 to 299 do
with Pos[i] do
begin
// cds es el ClientDataSet
if cds.Locate('U_ID', i+1, [loCaseInsensitive]) then
cds.Edit
else
begin
cds.Insert;
cds.FieldByName('U_ID').AsInteger := i+1;
end;

// la tabla tiene 2 campos U_ID Integer y DATA Blob.
blobF := cds.FieldByName('DATA') as TBlobField;
bs := cds.CreateBlobStream(blobF, bmWrite);
try
bs.Write(Pos[i], SizeOf(Pos));
finally
bs.Free;
end;
cds.Post;
end;
end;


Alguna idea?

mamcx
24-02-2016, 16:26:49
1.000 no tiende a ser un numero significativo para manejar en memoria para tipos de datos basicos. Quizas dependa de que tan grande son los datos en el BLOB.

Si se te esta incrementando la memoria, es porque hay un leak. No estas liberando algo, o tienes una referencia circular que te impide hacerlo.


Si hay otras maneras? Seguro. Pero implica salir del esquema de los DataSets y para lo que describes no parece necesario, si es que se encuentra la razon del aumento de memoria (Y cuanto es este aumento? Cuanta memoria disponen los equipos que usaran esto?)

jars
24-02-2016, 18:15:21
Lo del aumento de memoria resulto solucionarse con cds.Refresh luego del ApplyUpdates.
Lo que no me convence mucho es el tiempo de demora al volcarlo a la BBDD, 300 registros 20 segundos.

ElKurgan
25-02-2016, 07:22:58
Ah, que hay campos blob implicados... hummmm

En este post (http://gedeveloper.blogspot.com.es/2009/08/performance-em-clientdataset-com-campo.html)hablan de algo parecido, aunque en este caso hablan de un retardo de 20 segundos para ¡¡ 19.000 registros !!

Parece que cada vez que haces el ApplyUpdates se actualizan en la BD los campos blob que no se han tocado (según el código). Lo mismo les pasaba a los del Post anterior.

Fíjate que dicen que lo solucionaron poniendo "poFetchBlobOnDemand" en el datasetProvider, y el tiempo se redujo drásticamente.

Si no es ese el problema, habrá que ver otro enfoque...

Saludos

jars
25-02-2016, 13:25:58
Lo que veo es que el blob (registro) es muy grande y no me permite crear mas de 380 registros (Access violation).
Alguien me puede decir como tomar ese registro, comprimirlo con ZLib y luego meterlo en el campo blob?

jars
25-02-2016, 13:27:49
[elkurgan] los campos blob se actualizan siempre. Gracias

Casimiro Notevi
25-02-2016, 13:48:06
¿Qué almacenas en esos campos blob?

jars
25-02-2016, 13:50:33
Lo que almaceno en el campo blob es un registro bastante grande con datos de todo tipo.

Casimiro Notevi
25-02-2016, 14:17:57
Lo que almaceno en el campo blob es un registro bastante grande con datos de todo tipo.
Preguntabas si se pueden comprimir esos datos, por eso mi duda. Entonces, evidentemente, si es un zip, un jpg, etc. no podrás comprimirlo. Si es texto, sí.

Me temo que el método que estás usando no es el más indicado para trabajar con campos blob.

jars
25-02-2016, 14:25:26
Lo que quiero comprimir no es ni zip ni jpg es un registro como este (una parte):


TPos = record
PosNum: Word;
AgentId: string[10];
AgentName: string[30];
State: Byte;
PosFlags: set of TPosFlag;
SkillSet: TSkillSet;
SkillsDistribution: TSkillsDist;
CurrentCallSkill: Byte;
CurrentStateTime: Word;
PosIdleTime: Word;
PosLockedTime: Word;
.......
sigue bastante mas



Lo que quería probar ahora era comprimirlo antes de meterlo en el campo blob y no se como hacerlo.

Por otro lado si este no es el método indicado acepto cualquier sugerencia para cambiarlo.

Casimiro Notevi
25-02-2016, 15:42:05
Sería necesario conocer bastante más detalles de tu sistema para poder recomendarte algo.

El código que has puesto no nos dice nada sobre lo que guardas el blob. Si son esos "record", entonces es texto. La verdad es que no se entiende bien qué estás haciendo. Sería necesaria más información.

jars
25-02-2016, 16:51:44
Voy a tratar de hacerme entender.
El programa recolecta información desde otro módulo a un ritmo de 2 veces por segundo, procesa esa información, la vuelca en el registro en cuestion y envía ese registro a distintos puestos via tcp. Esto viene funcionando hace años y bien.
La cosa es que ahora se necesita cada tanto tiempo (pej. 1 minuto) bajar ese registro a la BBDD para que en caso de corte de luz o cualquier otro problema, no se pierda la información acumulada hasta el momento. Este programa lleva una estadística diaria y no se puede perder es por eso que se necesita resguardarla en BBDD para que a lo sumo se pierda el último minuto y no todo.
El registro tiene un tamaño de 230K (si, es grande), logre comprimirlo y lo deja en aproximadamente 10.5 k.
Mi última prueba fue de no manejar una tabla en memoria, directamente tomar el registro, comprimirlo y hacer un comando SQL Insert pero me encuentro que al llegar al registro 359 siempre se cuelga con un Acces violation en una direccion 005a2000 no hay nada ahi. Este es el código que estoy probando:



type
TPos = packed record
PosNumber: Word;
AgentId: string[10];
AgentName: string[30];
State: Byte;
ChatState: TChatState;
PosFlags: set of TPosFlag;
SkillSet: TSkillSet;
SkillsDistribution: TSkillsDist;
CurrentCallSkill: Byte;
IMRSessions: string[250];
CurrentStateTime: Word;
PosIdleTime: Word;
PosLockedTime: Word;
PosLockedAnswTime: Word;
......
end;

// tamaño total 230.000

procedure TRecord2BlobForm.btnUpdClick(Sender: TObject);
var
i: Integer;
s, zip: TMemoryStream;
begin
Q.SQL.Add('INSERT INTO SESSION_DATA VALUES(:ID, :DATA)');
Q.Prepare;

Sched.StartTransaction;
for i := 0 to 999 do
with Pos[i] do
begin
s := TMemoryStream.Create;
zip := TMemoryStream.Create;

try
s.Write(Pos[i], SizeOf(TPos));
s.Position := 0;
ZCompressStream(s, zip, zcMax);
Q.ParamByName('ID').Value := i;
Q.ParamByName('DATA').LoadFromStream(zip, ftBlob) ;
Q.ExecSQL;
finally
FreeAndNil(s);
FreeAndNil(zip);
end;
if i mod 50 = 0 then
begin
Sched.Commit;
Sched.StartTransaction;
end;
end;
if Sched.InTransaction then Sched.Commit;
end;


Espero me haberme hecho entender.
Gracias.

mamcx
26-02-2016, 01:32:29
Lo que quiero comprimir no es ni zip ni jpg es un registro como este (una parte):


Lo que te dice casimiro es muy cierto. Ahora, el porque no te esta funcionando el uso normal es porque en el esquema relacional basico es facil comparar los valores en cada celda (y es "economico" hacer comparacion entre antes y despues para valores estandar), pero el uso de BLOBs hace que un minimo cambio haga que toda esa fila tenga que actualizarse.

Esa es una de las razones por la que almacenar archivos se recomienda hacerlo por fuera de la tabla.

El tema de fondo, es que estas empaquetando un "micro base de datos" dentro de una celda de una fila de una tabla. Si lo comprimes? Si claro, queda mas pequeño y eso es buena para transferencias PERO, es el mismo lio: NO IMPORTA si son archivos o campos o texto: Estas metiendo una BD dentro de un campo.

Entiende que al final: Los campos son bytes, y sean archivos u otra cosa tienen una estructura. Al usar un blob estas "rompiendo" la estructura "normal" de una tabla y le estas metiendo algo "desconocido" como un valor. De ahi, que la BD no tiene ni idea de que hacer hasta que no desempaquetas y descifras ese valor.

Me sigues? Compresion o tipo de datos no es lo fundamental - de hecho, es tangencial-: Es que pasaste de un mundo a otro y ese otro mundo esta rompiendo con las reglas predecibles.

------

Que hacer? Ayuda que nos des mas info. Pero por la pinta de esto, hay 2 caminos basicos:

1- Convierte ese BLOB en tablas, relaciones y ahora si podra operar todo normal.
2- Separar logica y muy seguramente, fisicamente los datos BLOB de los normales, y aplicas logica extra a ese blob para saber si actualizas o no. Osea, tratas como si ese blob fuera un archivo, y asi como un motor SQL no sabe procesar JPGs, asi en este caso.

De esa manera, cambios en los datos basicos no interfieren con el blob y viceversa.

Y para actualizar el blob de forma eficiente? Aparte de usar tablas relacionales (que parece muy viable) puede hacer un DIFF para chequear cambios y solo extraer lo necesario y ejecutar un PATCH en el servidor junto con un HASH para certificar que la actualizacion fuer correcta y exitosa. O si el cambio en el blob es muy frequente, usar un LOG manual de transacciones donde describes los pasos aplicados en el cliente, y le haces un MERGE en el servidor.

Como con archivos.

mamcx
26-02-2016, 01:45:49
La cosa es que ahora se necesita cada tanto tiempo (pej. 1 minuto) bajar ese registro a la BBDD para que en caso de corte de luz o cualquier otro problema, no se pierda la información acumulada hasta el momento. Este programa lleva una estadística diaria y no se puede perder es por eso que se necesita resguardarla en BBDD para que a lo sumo se pierda el último minuto y no todo.
El registro tiene un tamaño de 230K (si, es grande), logre comprimirlo y lo deja en aproximadamente 10.5 k.

Con esta información entonces es muy claro que hacer: Que es lo que permite en una BD que la información sobreviva a accidentes y que se procese esto de forma correcta? Transacciones? NOPE: Log de transacciones!

Te recomiendo que leas este articulo de gente especializada en ese tema:

https://martin.kleppmann.com/2015/03/04/turning-the-database-inside-out.html

El punto clave aqui es que necesitas generar un LOG, una estructura que solo sigue hacia adelante (que solo hace "append"), que describe precisamente los datos y cambios ejecutados, en orden cronologico y que luego ejecutas en el servidor esos pasos (asi es como hacen las BD relacionales para procesar lo que hacen).

Esto es MUY facil de programar. Necesitas hacer algo asi



TimeStamp Action Data
10:51 - UPDATE - AgentName = "A", State = 1, ...
10:52 - UPDATE - AgentName = "B", State = 2, ...


Un log es correcto si ejecutando de principio a fin obtienes los valores exactos. Veras que es una correspondendia de 1-1 entre lo que recibes de tus sensores y lo que mandas.

Si se cae la conexión o pasa algo, aun si el servidor perdio la "pista" de donde estaba operando, simplemente reseteas todo y re-ejecutas tu log. Ves? Un log es un BACKUP!.

Para optimizar puedes decir: Ejecuta LOG desde X a Y y solo procesas lo ultimo. Seria ideal si en cada paso tienes un checksum ( Guardas en cada linea de log un checksum entre esa linea y el anterior: Así siempre se puede garantizar que al aplicar la linea todo ok! aun si estas procesando solo una sección)

De tanto en tanto, tienes que compactar el log, ponerle por decirlo así "Saldo inicial" y seguir el proceso.

Usando esto se pueden procesar GIGABYTES POR SEGUNDO en un PC ordinario. Es MUY eficiente. Incluso, puedes procesar el log fuera de banda, por multiples programas (unos que reportean, otros que calculan, etc), de forma concurrente sin miedo a nada y asi por el estilo.

Leete el articulo que es de lo mejor que ha salido de este tema y es muy explicativo. La programación es muy trivial, ademas, una vez que entiendes como hacerlo ;)