Buscar

Limpiar filtro
Artículo
Ariel Arias · 22 nov, 2022

IAM & IRIS / IRIS Health desde un mismo archivo YML

Disclosure Statement: Sugerencias para relalizar pruebas en ambientes usados para demostraciones o desarrollos, no en ambientes productivos. Caso de uso: teniendo IAM, lo ejecutamos desde un archivo YML, y necesitamos que se conecte a una Instancia IRIS en seguida, pero IRIS tiene deshabilitado el usuario IAM y la aplicación IAM. Alternativa: Modificamos el archivo YAML para agregar la imagen IRIS / IRIS for Health que queremos utilizar, nos aseguramos que usen la misma red; ya sea que la hemos definido previamente y queremos acoplar estas nuevas imágenes a esta red, o creamos una nueva para aislar estas nuevas imágenes en un ambiente propio; y lo principal: ejecutamos un script para habilitar el usuario y la aplicación IAM de modo que al iniciar quede todo el ambiente operativo. Requisito: Acceso a WRC para descarga de imagen de IAM (IRIS API Manager) Distribution page PASO A PASO Lo primero es comentar que la primera vez que quise usar el script que viene con el mismo instalador, me encontré con dos inconvenientes (para mi, es probable que quien tiene mayor conocimiento de Docker y Kubernetes no lo vea como inconveniente) y es que, por un lado, quería conectar este IAM con una instancia de IRIS que ya estaba en ejecución y con algunas API desarrolladas, entonces esperaba que fuera "ejecutar y conectar", y me percaté que: - El usuario IAM y la aplicación IAM de mi instancia están deshabilitados. - Al momento de ejecutar con "docker compose up -d", se crea una nueva red con el nombre "scripts_default", entonces si quiero que se comunique con mi instancia (otra imagen docker de IRIS en otra red llamada "irisenv-net" debo ejecutar unos pasos adicionales. Una alternativa que encontré y me llevó a escribir este artículo, es, primero crear una red en docker que será utilizada en adelante por mis imagenes docker network create -d bridge irisenv-net Y luego modificar el docker-compose.yml para utilizar una red ya creada por docker y que usan otras imagenes en ejecución, para ello, agregué al archivo lo siguiente: networks: irisenv-net: external: true Con esto, las imagenes que usen la red "irisenv-net" se comunicarán entre ellas sin pasos adicionales. Entonces para cada imagen del archivo, agregué también: networks: - irisenv-net Me quedaba pendiente lo del usuario y aplicación IAM, que al ser una instancia ya en ejecución debía ejecutar manualmente tanto la habilitación como el establecer la contraseña que usaría mi usuario IAM, pues hay que pasarle ese parámetro al script. Pero, ¿Si no está en ejecución?, ¿Puedo desde un mismo script levantar una imagen de IRIS, al mismo tiempo que levanto el IAM y que queden automáticamente conectadas y además el IAM tome la licencia existente en IRIS?: Mi respuesta fue SI. De la siguiente forma: - Agregar en un mismo archivo (docker-iris.yml por ejemplo) la llamada a la imagen de IRIS que voy a utilizar, luego las que requiere IAM y las de IAM debo hacerlas depender de IRIS, entonces es un paso de copiar y pegar y modificar para dejar todo establecido. Hasta acá tenemos un archivo que al ser ejecutado iniciará 4 imagenes: IRIS, IAM-MIGRATION, IAM y DB Entonces, ¿Cómo resolvemos que al iniciar la instancia de IRIS, además de cargar la licencia, habilite el usuario IAM con una contraseña que conozco y puedo pasarle como parámetro al IAM, y además se habilite la aplicación IAM?. Respuesta: con un script que se ejecute al momento de iniciar la imagen de IRIS, es decir, tenemos un archivo docker llamado "irisdpfile" que contiene el detalle de la imagen que usaremos y agregamos la llamada al script. El script que vamos a utilizar: zn "%SYS" Do ##class(Security.Users).UnExpireUserPasswords("*") set st=$SYSTEM.Security.ChangePassword("IAM","iampassword") set iamuser = ##class(Security.Users).%OpenId("iam") set iamuser.Enabled = 1 set iamuser.AccountNeverExpires = 1 Set st = iamuser.%Save() Set iamapp = ##class(Security.Applications).%OpenId("/api/iam") Set iamapp.Enabled = 1 Set st = iamapp.%Save() zn "USER" Si vemos el detalle, en este archivo, habilitamos los usuarios que puedan estar expirados (por ejemplo el "superuser"); luego establecemos una nueva password para el usuario IAM y finalmente habilitamos la aplicación "/api/iam". Este archivo, lo guardé en el mismo directorio desde donde estoy ejecutando el "docker-iris.yml" y lo nombré: "enableirisuser.script" De esta forma, desde el dockerfile de IRIS (irisdpfile) agregué las líneas: USER irisowner COPY ./enableirisuser.script /tmp/enableirisuser.script RUN iris start IRIS \ && iris session IRIS < /tmp/enableirisuser.script \ && iris stop IRIS quietly USER irisowner Nota: Deben quedar en una misma línea de ejecución el iniciar la instancia y llamada al script, me pasó que quise hacer todo por separado y no me dio resultado. Así, logré desde un mismo archivo, levantar una instanacia de IRIS y de IAM, donde IAM lograba comunicarse con IRIS sin problema y dejar en seguida habilitada la licencia para poder crear workspaces, ambiente de desarrolladores, y todo lo que sea necesario desde IAM. docker-compose -f docker-iris.yml up -d Les comparto cómo quedó mi archivo yml finalmente: version: "3.7" services: irishost: depends_on: - irishealth build: context: . dockerfile: irisdpfile container_name: irishost volumes: - type: bind source: ./Licencias/ target: /licencias - type: bind source: ../sharingweb/T2017/ target: /common_shared - type: bind source: ./wwwshared/ target: /wwwfiles command: --key /licencias/iris.key --check-caps false environment: IRIS_MASTER_HOST: APPTNOTSrv # DNS based on the name of the service! IRIS_MASTER_PORT: 51773 IRIS_MASTER_USERNAME: SuperUser IRIS_MASTER_PASSWORD: SYS IRIS_MASTER_NAMESPACE: APPINT IRIS_USERNAME: SuperUser IRIS_PASSWORD: sys WEBGW_HOST: webgateway2022 IRIS4H_HOST: irishealth ports: - "3071:1972" # 51773 is the superserver default port - "3072:52773" # 52773 is the webserver/management portal port - "3171:53773" # 53773 is the JDBC Gateway port tty: true links: - irishealth:irishealth networks: - irisenv-net iam-migrations: depends_on: - db image: intersystems/iam:2.8.1.0-3 container_name: iam-migrations hostname: iam-migrations command: kong migrations bootstrap environment: KONG_DATABASE: postgres KONG_PG_DATABASE: ${KONG_PG_DATABASE:-iam} KONG_PG_HOST: db KONG_PG_PASSWORD: ${KONG_PG_PASSWORD:-iam} KONG_PG_USER: ${KONG_PG_USER:-iam} KONG_CASSANDRA_CONTACT_POINTS: db ISC_IRIS_URL: 'IAM:iampassword@irishost:52773/api/iam/license' ISC_CA_CERT: ${ISC_CA_CERT} restart: on-failure links: - db:db restart: on-failure networks: - irisenv-net iam: depends_on: - irishost - db image: intersystems/iam:2.8.1.0-3 container_name: iam hostname: iam environment: KONG_ADMIN_ACCESS_LOG: /dev/stdout KONG_ADMIN_ERROR_LOG: /dev/stderr KONG_ADMIN_LISTEN: '0.0.0.0:8001' KONG_ANONYMOUS_REPORTS: 'off' KONG_CASSANDRA_CONTACT_POINTS: db KONG_DATABASE: postgres KONG_PG_DATABASE: ${KONG_PG_DATABASE:-iam} KONG_PG_HOST: db KONG_PG_PASSWORD: ${KONG_PG_PASSWORD:-iam} KONG_PG_USER: ${KONG_PG_USER:-iam} KONG_PROXY_ACCESS_LOG: /dev/stdout KONG_PROXY_ERROR_LOG: /dev/stderr KONG_PORTAL: on KONG_VITALS: on KONG_PORTAL_GUI_PROTOCOL: http KONG_PORTAL_GUI_HOST: 'localhost:8003' KONG_ADMIN_GUI_URL: 'http://localhost:8002' ISC_IRIS_URL: 'IAM:iampassword@irishost:52773/api/iam/license' ISC_CA_CERT: ${ISC_CA_CERT} irishost: '172.26.0.2' ports: - target: 8000 published: 8000 protocol: tcp - target: 8001 published: 8001 protocol: tcp - target: 8002 published: 8002 protocol: tcp - target: 8003 published: 8003 protocol: tcp - target: 8004 published: 8004 protocol: tcp - target: 8443 published: 8443 protocol: tcp - target: 8444 published: 8444 protocol: tcp - target: 8445 published: 8445 protocol: tcp restart: on-failure networks: - irisenv-net links: - db:db db: image: postgres:9.6 container_name: db hostname: db environment: POSTGRES_DB: ${KONG_PG_DATABASE:-iam} POSTGRES_PASSWORD: ${KONG_PG_PASSWORD:-iam} POSTGRES_USER: ${KONG_PG_USER:-iam} volumes: - 'pgdata:/var/lib/postgresql/data' healthcheck: test: ["CMD", "pg_isready", "-U", "${KONG_PG_USER:-iam}"] interval: 30s timeout: 30s retries: 3 restart: on-failure networks: - irisenv-net depends_on: - irishost stdin_open: true volumes: pgdata: external: true networks: irisenv-net: external: true Y el "irisdpfile": FROM intersystems/iris:2022.1.0.209.0 LABEL maintainer="Ariel Arias <ariel.arias@intersystems.com>" USER root RUN apt-get -y update # Install some aditional software may be needed later RUN apt-get install -y vim nano net-tools unzip wget sudo iputils-ping links RUN echo "FROM IRISDPFILE" USER irisowner COPY ./Licencias/iris4h2023.key /usr/irissys/mgr/iris.key COPY ./enableirisuser.script /tmp/enableirisuser.script RUN iris start IRIS \ && iris session IRIS < /tmp/enableirisuser.script \ && iris stop IRIS quietly USER irisowner # add default health check HEALTHCHECK --interval=1m --timeout=10s --start-period=1m --retries=3 \ CMD /irisHealth.sh || exit 1 USER irisowner EXPOSE 1972 EXPOSE 52773 EXPOSE 53773
Artículo
Nancy Martínez · 25 sep, 2020

Opciones de descarga de archivos por FTP (todos los archivos, número de archivos): copiar archivo o mover archivo

Método: para descargar el archivo FTP desde Caché de InterSystems: Si tienes cualquier consulta, deja un mensaje por favor: ClassMethod FTPDownload(myFTP = "", myUserName = "", myPassword = "", sFileLocation = "", dLocation = "", noOfdownloadFile = 1, sourceFileDel = 0){ /*--------------------------------------------------------------------------------------------------------------------------- descargar el archivo según los requirimientos: FTP Método: Resuable creado por: Sanjib Raj Pandey el 30/03/2018 downLoadFile = Número de archivos o todos los archivos ...... que quieras descargar; EL VALOR PREDETERMINADO ES 1 = indica un valor, por ej. 1,3,7,100 archivos = escribe "*" para descargar todos los archivos. SourceFileDel = Si quieres eliminar los arhivos del directorio de origen después de la descarga..... entonces configura el valor 1 -- ; el valor predeterminado es: 0 1= True (elimina el archivo de la carpeta de origen después de la descarga), 0 = False (solo copiar) sFileLocation = Ubicación de origen del archivo (carpeta) dLocation = Carpeta de destinoEjemplo: La siguiente línea ..... mueve todos los archivos desde el origen a la ubicación de destino w ##class(CW.COMMON).FTPDownload("Dirección IP","Usuario","Password","Ruta origen","Ruta destino,"*",1) La siguiente línea...... copia 200 archivos desde la carpeta de origen a la de destinow ##class(CW.COMMON).FTPDownload("Dirección IP ","Usuario","Password","Ruta origen","Ruta destino",200,0) ------------------------------------------------------------------------------------------------------------------------- */ // puedes configurar el control de errores a, por ejemplo, Try and Catch Set (count,fileNo,key,messge,fileStream,myFileName,myFile,fSave,eMessage,eSubject)="" Set fIp= myFTP Set fUserName= myUserName set fPassword=myPassword set sFileLocaion=sFileLocation set dLocation=dLocation Set downloadFile=noOfdownloadFile Set sourceFileDel=sourceFileDel If $Length(fIp)=0||($L(fUserName)=0)||($L(fPassword)=0) || ($L(downloadFile)=0) Q "Credenciales inválidas o el archivo de descarga es 0.!, verifica la IP, Usuario, password o Ubicación o destino del FTP!" Set myFtp=##class(%Net.FtpSession).%New() Set eMessage="La conexión FTP falló, por favor comprueba "_fIp_" o el usuario, password.!" Set eSubject ="Mensaje de advertencia de FTP." Set myFtp.Timeout = 60 If 'myFtp.Connect(fIp,fUserName,fPassword) Quit w $$EVEMAIL^CW.COMMON(eSubject,eMessage) Do myFtp.SetDirectory(sFileLocaion) If 'myFtp.NameList(" ",.x) Quit "Fichero(s) no encontrados " Set fileStream = ##class(%Stream.FileBinary).%New() Set message ="copiado" Set myFileName="" Set fileNo=0 Set Key="" If (downloadFile = "*") { While (x.GetNext(.Key))'="" { Do StartCopy }Do myFtp.Logout()Quit fileNo_" Fichero(S) se han "_message_"con éxito !" } If (downloadFile >0) { Set count=1 While ((count <= downloadFile) && (count<=x.Count())) { do StartCopy Set count=count +1 } Do myFtp.Logout() Quit fileNo_" Fichero(S) se han "_message_"con éxito !" } StartCopy Set myFileName= x.GetNext(.fileNo) Do myFtp.Binary() Do myFtp.Retrieve(myFileName,.fileStream) Set myFile= ##class(%Library.FileBinaryStream).%New() Set myFile.Filename=dLocation_myFileName Do myFile.CopyFrom(fileStream) Set fSave=myFile.%Save() IF ((sourceFileDel=1) && (fSave = 1)) { Do myFtp.Delete(myFileName) Set message="Movidos"}
Artículo
Ricardo Paiva · 28 oct, 2021

Generadores de documentación estática

¡Hola comunidad! En el pasado, la documentación técnica del código fuente y de los productos de software se generaba en archivos chm, pdf y generadores de documentación de los propios lenguajes de programación. Este enfoque antiguo tenía las siguientes limitaciones: Documentación obsoleta, Documentación no interactiva y difícil de consultar, Diseño poco amigable y que no se adhiere a HTML, Imposibilidad de personalizar el diseño de los documentos, Imposibilidad de tener documentación en HTML 5 online y offline. Falta de soporte a Markdown. Hoy en día, existen varias soluciones de generación de documentación que producen Portales de Documentación Web muy atractivos, interactivos y con opciones estáticas y dinámicas, con soporte total para HTML 5 y más recientemente Markdown. Consulta la tabla con las opciones de código abierto más populares: Documentation Product Stars MkDocsGithub Repo: https://github.com/mkdocs/mkdocs Static and dynamic documentation generation Simple and lightweight and built in Python Smart full-text search box Multiple themes and plug-ins Support to Markdown and HTML Integration with Git pages Extensible using Python SPA architecture 11.4k DocsifyGithub Repo: https://github.com/docsifyjs/docsify No statically built html files Simple and lightweight (~21kB gzipped) Smart full-text search plugin Multiple themes Useful plugin API Compatible with IE11 Support SSR Support embedded files 16.5k DocusaurusGithub: https://github.com/facebook/docusaurus/ Powered by Markdown Built Using React Ready for Translations Document Versioning Document Search Quick Setup 21.4k SlateGithub: https://github.com/slatedocs/slate Clean, intuitive design Single page documentation Markdown support Out-of-the-box syntax highlighting Write code samples in multiple languages Automatic, smoothly scrolling table of contents Documentation is editable by users via Github RTL Support 31.9k GitBookhttps://www.gitbook.com/ Clean, intuitive design Collaborative work/text edition Support to drafts and versioning Single page documentation Markdown support Out-of-the-box syntax highlighting Write code samples in multiple languages Automatic, smoothly scrolling table of contents The better integration with Github Multiple output formats (PDF, HTML5, epub,etc.) Cloud hosting, with free plans to personal projects and paid plans to business use cases 500,000 users La aplicación IRIS Publisher, en Open Exchange, permite extraer bloques de documentación XData en HTML o Markdown y generar un sitio web con la documentación de tu app, utilizando MkDocs. Consulta las instrucciones en el siguiente artículo de esta serie: Cómo crear el Portal de Documentación para InterSystems IRIS Referencia: 5 free static documentation generators you must check out
Artículo
Ricardo Paiva · 16 sep, 2021

Base de datos documental (DocDB) - Ejemplos de llamadas API REST - Colección Postman

Como ayuda para aquellos que quieren utilizar las funciones de la [Base de datos documental](https://docs.intersystems.com/irislatest/csp/docbook/Doc.View.cls?KEY=GDOCDB_intro) (DocDB) dentro de InterSystems IRIS, y específicamente la [API REST](https://docs.intersystems.com/irislatest/csp/docbook/Doc.View.cls?KEY=GDOCDB_rest) que proporciona, reuní una [Colección](https://www.postman.com/product/api-client/) [Postman](https://www.postman.com/collection/) que ofrece muestras para varias llamadas básicas. Por ejemplo: ![](/sites/default/files/inline/images/images/image(1316).png) En el ejemplo se utilizan documentos de "Color", por ejemplo, Rojo, Azul, etc., utilizando una estructura JSON de muestra desde [aquí](https://www.sitepoint.com/colors-json-example/). La Colección incluye llamadas de diferentes "categorías": * Crear metadatos: crear la base de datos y las propiedades relacionadas * Obtener metadatos: entender qué bases de datos y propiedades están definidas * CUD: crear/actualizar/eliminar documentos * Buscar y obtener documentos: recuperar documentos según el ID o ciertos valores o criterios * Eliminar metadatos: eliminar propiedades o bases de datos El orden de las solicitudes en la Colección tiene cierta lógica interna (por ejemplo, primero crear la base de datos y las propiedades, luego insertar algunos datos y después recuperarlos), pero por supuesto puedes utilizarlos en el orden o los cambios que desees. El orden también funciona bien si se ejecuta [Collection Runner](https://learning.postman.com/docs/running-collections/intro-to-collection-runs/) de Postman. Añadí algunos [scripts de prueba](https://learning.postman.com/docs/writing-scripts/test-scripts/) básicos, que permiten que Postman muestre el estado Aprobado o Fallido para cada llamada. Por ejemplo: ![](/sites/default/files/inline/images/images/image(1317).png) Ten en cuenta que la última llamada **elimina todas las bases de datos de documentos dentro de un Namespace**, así que no ejecutes esto a menos que realmente quieras hacerlo... no lo hagas manualmente y tampoco como parte de la ejecución de toda la Colección. Para conseguir que las llamadas funcionen en varios entornos, utilicé la función [variables](https://learning.postman.com/docs/sending-requests/variables/) de Postman. Esto permite cambiar el nombre/la IP del servidor, el puerto y el namespace - ![](/sites/default/files/inline/images/images/image(1320).png) Así que cada llamada se parece a esto: ![](/sites/default/files/inline/images/images/image(1321).png) Seguramente también necesitarás adaptar la parte de autenticación. Actualmente tengo "Basic Authentication" simplemente con "SuperUser" y "sys": ![](/sites/default/files/inline/images/images/image(1327).png)   Solo para añadir un _**cambio "multimodelo"**_ a esto, aquí también hay un ejemplo de acceso a estos datos a través de SQL: Esta es la estructura de la tabla (como se ve en la extensión SQL Tools en VSCode): ![](/sites/default/files/inline/images/images/image(1322).png) Este es un resultado SELECT sencillo: ![](/sites/default/files/inline/images/images/image(1323).png) Y aquí hay un SELECT con una cláusula WHERE sobre una de las propiedades que definimos: ![](/sites/default/files/inline/images/images/image(1324).png)
Pregunta
Yone Moreno · 28 mar, 2022

TCP y DICOM: Investigar opciones de invocación del servicio

Buenos días, Agradeceríamos el apoyo de ustedes: Desarrollando una integración para realizar un circuito "Query / Retrieve" con estudios de imágenes médicas DICOM, necesitaríamos lo siguiente: Opciones de invocación del servicio DICOM TCP que se ha publicado para esta integración, alternativas a la opción por linea de comando En concreto hemos leído: Recibir documento DICOM con un PDF embebido y metadatos Adaptando el ejemplo, empleamos la línea: ./storescu -b VNAPRE -c ESBPRE@10.136.4.XYZ:19ABC ./embeddedpdf.dcm Mediante la cual simulamos el envío de un documento DICOM con un PDF Sin embargo, esto no nos es fructífero, provechoso o de solución, ya que lo que se necesita actualmente, es simular consultas del tipo "FIND". En concreto necesitaríamos consultas del tipo "Obtener Resultados a nivel de Estudios" StudyRootQuery - FIND u "Obtener Estudios a nivel de Pacientes" PatientRootQuery - FIND // StudyRootQuery - FIND set tAffectedSOPClassUID="1.2.840.10008.5.1.4.1.2.2.1" // PatientRootQuery - FIND ;set tAffectedSOPClassUID="1.2.840.10008.5.1.4.1.2.1.1" Hemos indagado cómo simular las llamadas FIND: Query / Retrieve scenario En particular probamos desde la "Salida" del Studio enviando el expediente del paciente en el campo "PatientID" del "DataSet" del documento DICOM: do ##class(DICOM.BS.QueryService).TestFind("13168299") Y de esta forma simularíamos una llamada al Servicio "dummy" cuya clase es: DICOM.BS.QueryService Sin embargo, ¿ qué opciones de invocación del servicio DICOM TCP que se ha publicado para esta integración existen ? ¿ Existe un SoapUI / POSTMAN / herramienta línea comando y/o visual, que nos permita simular comandos FIND de DICOM por TCP ? Gracias por leernos y por sus respuestas Además hemos leído: https://docs.intersystems.com/latest/csp/docbook/DocBook.UI.Page.cls?KEY=EDICOM_intro#EDICOM_intro_samples https://docs.intersystems.com/latest/csp/docbook/DocBook.UI.Page.cls?KEY=EDICOM_storage_production https://docs.intersystems.com/latest/csp/docbook/DocBook.UI.Page.cls?KEY=EDICOM_associations https://es.community.intersystems.com/post/dicom-no-hay-asociaci%C3%B3n-activa-para-calling-aet-yyy-y-called-aet-aaa-%E2%B8%98-%E2%80%BD https://community.intersystems.com/post/dicom-tcp-services-and-dicom-implementation-details-ensemble A nivel visual, ahora estamos probando el circuito mediante un Servicio "dummy", cuya clase es: DICOM.BS.QueryService Sin embargo, necesitamos encontrar la vía de simular ser el sistema origen, es decir, probar el servicio TCP, siendo de la clase: EnsLib.DICOM.Service.TCP ¿ Qué opciones de invocación del servicio DICOM TCP que se ha publicado para esta integración, alternativas a la opción por linea de comando, existirían ? ¿ De qué forma lo harían ustedes ? ¿Disponen de ejemplos, documentación, código, referencias o indicaciones que pudieran servir de apoyo? Muchas gracias de antemano Un saludo Hola Yone, Imagino que te refieres al ejemplo https://github.com/intersystems-ib/iris-dicom-sample Los comandos C-FIND son mensajes DICOM que deben tener el formato correcto y transmitirse usando el protocolo DICOM. Por eso, o usas un simulador, o generas ese mensaje desde Ensemble / Health Connect. En el ejemplo, el comando do ##class(DICOM.BS.QueryService).TestFind() provoca la generación de un mensaje C-FIND dentro de la producción para un paciente concreto. El simulador dcm4che tiene la utilidad findscu que permite simular la generación de mensajes C-FIND.
Artículo
Muhammad Waseem · 8 ago, 2022

Cómo medir las emisiones de gases de efecto invernadero (GHG) con la aplicación Carbon Footprint Counter

La aplicación Carbon Footprint Counter utiliza el GHG Protocol para medir las emisiones de carbono en las empresas. El Protocolo de Gases de Efecto Invernadero (GHG Protocol) establece marcos estandarizados globales integrales para medir y gestionar las emisiones de gases de efecto invernadero (GHG) de las operaciones del sector público y privado, las cadenas de valor y las acciones de mitigación. Sobre la base de una asociación de 20 años entre el Instituto de Recursos Mundiales (WRI) y el Consejo Empresarial Mundial para el Desarrollo Sostenible (WBCSD), el GHG Protocol trabaja con gobiernos, asociaciones industriales, ONGs, empresas y otras organizaciones. (fuente: https://ghgprotocol.org/about-us). La aplicación Carbon Footprint Counter utiliza InterSystems IRIS para implementar APIs REST y la base de datos SQL para administrar el inventario de emisiones de carbono en las empresas. La interfaz es Angular 12 con el framework PrimeNG (líder de código abierto para Angular). Factores de emisión Para medir las emisiones de carbono de la empresa, la aplicación almacena 300 factores de emisión distribuidos en 4 segmentos (inmóvil, móvil, transporte y electricidad comprada). Cada factor, como el gas natural, la gasolina, el transporte en autobús, el transporte aéreo, los viajes en automóvil, etc., tiene valores de referencia para calcular las emisiones de co2, ch4 y n2o. Así: Emisiones inmóviles (stationary) El GHG Protocol define inmóvil como el consumo de combustible en una instalación para producir electricidad, vapor, calor o energía. La combustión de combustibles fósiles por parte de calderas de gas natural, generadores diésel y otros equipos emite dióxido de carbono, metano y óxido nitroso a la atmósfera. Puedes registrar tus emisiones inmóviles en el menú y la ventana de Combustión inmóvil (Stationary Combustion): Emisiones de combustión móvil El GHG Protocol define la combustión móvil como el consumo de combustible de los vehículos que son propiedad de la empresa o alquilados. La combustión de combustibles fósiles en vehículos (incluyendo automóviles, camiones, aviones y barcos) emite dióxido de carbono, metano y óxido nitroso a la atmósfera. Puedes registrar tus emisiones móviles en el menú y la ventana de Combustión móvil: Emisiones del transporte El GHG Protocol define el transporte como el consumo de combustible de los vehículos utilizados para realizar viajes de empresa. Los ejemplos incluyen viajes aéreos comerciales y el uso de vehículos alquilados durante viajes de negocios. Puedes registrar tus emisiones de transporte en el menú y la ventana de Transporte: Emisiones de electricidad El GHG Protoco define las emisiones de electricidad como energía comprada a las empresas de servicios locales (no se quema en el sitio). Los ejemplos incluyen electricidad, vapor y agua fría o caliente. Para generar esta energía, las empresas de servicios queman carbón, gas natural y otros combustibles fósiles, emitiendo dióxido de carbono, metano y óxido nitroso en el proceso. Puedea registrar tus emisiones de electricidad en el menú y la ventana de Emisiones de electricidad: Cálculo de las emisiones totales de carbono La aplicación computa todas las emisiones registradas: Pasar a la acción Ahora, tu empresa puede saber sus emisiones de carbono anuales y tomar medidas para compensar las emisiones. Se pueden comprar créditos de carbono, hacer donaciones a WWF, Greenpeace u otras instituciones... Calcúlalo ahora usando https://openexchange.intersystems.com/package/Carbon-Footprint-Counter.
Artículo
Alberto Fuentes · 15 mar, 2023

Cómo recorrer la estructura de datos de un global de IRIS desde Python usando el SDK nativo de IRIS para Python

InterSystems IRIS 2022.2 tiene un SDK nativo para Python (https://docs.intersystems.com/iris20222/csp/docbook/Doc.View.cls?KEY=PAGE_python_native). Sabemos cómo recorrer la estructura de datos de un global usando la función $Order de ObjectScript. SET key="" FOR { SET key=$ORDER(^myglobal(key)) QUIT:key="" WRITE !,^myglobal(key) } ¿Cómo hacer lo mismo desde Python usando el SDK nativo de IRIS para Python? Aquí va un ejemplo directamente en Python: import iris args = {'hostname':'127.0.0.1', 'port':51772, 'namespace':'USER', 'username':'_SYSTEM', 'password':'SYS' } conn = iris.connect(**args) # Create an iris object irispy = iris.createIRIS(conn) # Create a global array in the USER namespace on the server irispy.set('A', 'root', 'foo', 'SubFoo') irispy.set(123, 'root', 'bar', 'lowbar', 'UnderBar') irispy.set(124, 'root', 'bar', 'lowbar', 'UnderBar2') irispy.set("hi", 'root', 'bar', 'lowbar') irispy.set("hi again", 'root', 'bar3') # Read the values from the database and print them subfoo_value = irispy.get('root', 'foo', 'SubFoo') underbar_value = irispy.get('root', 'bar', 'lowbar', 'UnderBar') underbar2_value = irispy.get('root', 'bar', 'lowbar', 'UnderBar2') lowbar_value = irispy.get('root', 'bar', 'lowbar') bar3_value = irispy.get('root', 'bar3') print('Created two values: ') print(' root("foo","SubFoo")=', subfoo_value) print(' root("bar","lowbar","UnderBar")=', underbar_value) print(' root("bar","lowbar","UnderBar2")=', underbar2_value) print(' root("bar","lowbar")=', lowbar_value) print(' root("bar3")=', bar3_value) direction = 0 # direction of iteration (boolean forward/reverse) next_sub = chr(0) # start at first possible subscript subs = [] print("\n Iterating root \n") isDef = irispy.isDefined('root', *subs) while isDef: next_sub = irispy.nextSubscript(False, 'root', *subs, next_sub) # get first subscript if next_sub == None: # we finished iterating nodes on this tree branch, move a level up if len(subs) == 0: # no more things to iterate break next_sub = subs.pop(-1) # pop last subscript in order to continue iterating this level if irispy.isDefined('root', *subs, next_sub) == 11: print('root(',*subs, next_sub, ')=',irispy.get('root', *subs, next_sub)) continue continue isDef = irispy.isDefined('root', *subs, next_sub) if isDef in [10, 11]: # keep building subscripts for depth first search subs.append(next_sub) next_sub = chr(0) continue elif isDef == 1: # reached a leaf node, print it print('root(',*subs, next_sub, ')=',irispy.get('root', *subs, next_sub)) else: # def 0 is not really expected print("error") irispy.kill('root') conn.close() exit(-1) # Delete the global array and terminate irispy.kill('root') # delete global array root conn.close()
Artículo
Luis Angel Pérez Ramos · 23 nov, 2022

Cómo configurar un mirror programáticamente

**Antecedentes** | Versión | Fecha | Cambios | |:------- |:---------- |:------------------------------------------------------------------------------------------------------------------------------------------ | | V1 | 08/02/2022 | Lanzamiento Inicial | | V1.1 | 06/04/2022 | Generación de certificados con un archivo sh en vez de un pki-scriptUso de variables de entorno en los archivos de configuración | ¡Hola Comunidad! ¿Ya habéis configurado un entorno en mirror? ¿Tenéis una red privada, una dirección IP virtual y una configuración SSL? Después de hacer esto un par de veces, me di cuenta de que es muy largo, y hay muchos pasos que hay que realizar manualmente para generar certificados y configurar cada instancia de IRIS. Es un dolor de cabeza para cualquiera que tenga que hacer esto a menudo. Por ejemplo, un equipo de control de calidad podría necesitar un nuevo entorno por cada nueva versión de la aplicación que tenga que probar mientras que el equipo de soporte puede necesitar crear un entorno para reproducir un problema complejo. Definitivamente, necesitamos herramientas para crearlos rápidamente. En este artículo crearemos una muestra para configurar un *mirror* con: - Arbiter. - Primary. - Miembro failover del backup. - Miembro asíncrono de lectura-escritura de informes. - Configuración SSL para transferencias de *journal* entre nodos. - Red privada para el *mirror*. - Dirección IP virtual. - Una base de datos en mirror. ![network-schema](https://raw.githubusercontent.com/lscalese/iris-mirroring-samples/master/img/net-schema.png) A primera vista, parece un poco complejo y parece que hace falta una gran cantidad de código, pero no te preocupes. Hay librerías en OpenExchange para realizar fácilmente la mayoría de las operaciones. El propósito de este artículo es ofrecer un ejemplo de cómo adaptar el proceso a vuestras necesidades, pero no es una guía de prácticas recomendadas en materia de seguridad. Así que vamos a crear nuestra muestra. ### Herramientas y librerías - [config-api](https://openexchange.intersystems.com/package/Config-API): Esta librería se utilizará para configurar IRIS. Es compatible con la configuración del *mirroring* desde la versión 1.1.0. No vamos a dar una descripción detallada de cómo utilizar esta librería. Ya hay varios artículos [aquí](https://community.intersystems.com/post/environment-setup-config-api). En resumen, config-api se utilizará para crear archivos de configuración de plantillas IRIS (formato JSON) y cargarlos fácilmente. - [ZPM](https://openexchange.intersystems.com/package/ObjectScript-Package-Manager). - Docker. - OpenSSL. ### Página de Github Podéis encontrar todos los archivos de recursos necesarios en el repositorio [iris-mirroring-samples](https://github.com/lscalese/iris-mirroring-samples/). ### Preparación del sistema Clonad el repositorio existente: ```bash git clone https://github.com/lscalese/iris-mirroring-samples cd iris-mirroring-samples ``` Si preferís crear una muestra desde cero, en vez de clonar el repositorio, simplemente cread un nuevo directorio con subdirectorios: `backup`, y `config-files`. Descargad [irissession.sh](https://raw.githubusercontent.com/lscalese/iris-mirroring-samples/master/session.sh): ``` mkdir -p iris-mirroring-samples/backup iris-mirroring-samples/config-files cd iris-mirroring-samples wget -O session.sh https://raw.githubusercontent.com/lscalese/iris-mirroring-samples/master/session.sh ``` Para evitar la incidencia "permiso rechazado" más tarde, tenemos que crear el grupo `irisowner`, el usuario `irisowner`, y cambiar el grupo del directorio del *backup* a `irisowner` ```bash sudo useradd --uid 51773 --user-group irisowner sudo groupmod --gid 51773 irisowner sudo chgrp irisowner ./backup ``` Este directorio se utilizará como volumen para compartir una copia de seguridad de la base de datos después de que se configure el primer miembro *mirror* con los otros nodos. ### Obtención de una licencia de IRIS *Mirroring* no está disponible con la Edición Community de IRIS. Si aún no tenéis una licencia válida para el contenedor de IRIS, conectaos al [Centro de Soporte Internacional (WRC)](https://wrc.intersystems.com) con vuestras credenciales. Haced clic en "Actions" --> "Online distribtion", después en el botón "Evaluations" y seleccionad "Evaluation License". Completad el formulario. Copiad vuestro archivo de licencia `iris.key` en este directorio. ### Inicio de sesión en el Registro de Contenedores de Intersystems Por comodidad, utilizamos Intersystems Containers Registry (ICR) para extraer imágenes de Docker. Si no sabéis vuestro nombre de usuario\\contraseña de Docker, conectaos a [SSO.UI.User.ApplicationTokens.cls](https://login.intersystems.com/login/SSO.UI.User.ApplicationTokens.cls) con vuestras credenciales del Centro de Soporte Internacional (WRC), y podréis recuperar vuestro Token ICR. ```bash docker login -u="YourWRCLogin" -p="YourICRToken" containers.intersystems.com ``` ### Creación de la base de datos `myappdata` y un mapeo de globales De momento no vamos a crear la base de datos `myappdata`, únicamente estamos preparando la configuración para crearla al momento de crear el Docker. Para ello, simplemente creamos un archivo sencillo utilizando el formato JSON. La librería config-api se utilizará para cargarlo en las instancias de IRIS. Cread el archivo [config-files/simple-config.json](https://github.com/lscalese/iris-mirroring-samples/blob/master/config-files/simple-config.json) ```json { "Defaults":{ "DBDATADIR" : "${MGRDIR}myappdata/", "DBDATANAME" : "MYAPPDATA" }, "SYS.Databases":{ "${DBDATADIR}" : {} }, "Databases":{ "${DBDATANAME}" : { "Directory" : "${DBDATADIR}" } }, "MapGlobals":{ "USER": [{ "Name" : "demo.*", "Database" : "${DBDATANAME}" }] }, "Security.Services" : { "%Service_Mirror" : { /* Enable the mirror service on this instance */ "Enabled" : true } } } ``` Este archivo de configuración permite crear una nueva base de datos con la configuración predeterminada y hacer un mapeo del global `demo.*` en el *namespace* USER. Para más información sobre las funciones del archivo de configuración [config-api](https://openexchange.intersystems.com/package/config-api) consulta el [artículo](https://community.intersystems.com/post/environment-setup-config-api), relacionado o la [página de github](https://community.intersystems.com/post/environment-setup-config-api). ### El archivo Docker El archivo Docker se basa en la plantilla existente de [Docker](https://github.com/intersystems-community/objectscript-docker-template), pero necesitamos hacer algunos cambios para crear un directorio de trabajo, instalar las herramientas para el uso de la IP virtual, instalar ZPM, etc… Nuestra imagen IRIS es la misma para cada miembro *mirror*. El *mirroring* se establecerá en el contenedor empezando con la configuración correcta dependiendo de su función (primer miembro, backup de respaldo/failover o informe de lectura-escritura). Observa los comentarios en el Dockerfile: ```Dockerfile ARG IMAGE=containers.intersystems.com/intersystems/iris:2021.1.0.215.0 # No es necesario descargar la imagen desde WRC, se hará automáticamente desde ICR cuando se despliegue el contenedor. FROM $IMAGE USER root COPY session.sh / COPY iris.key /usr/irissys/mgr/iris.key # /opt/demo será nuestro directorio de trabajo y en el que almacenaremos nuestros archivos de configuración así como otros archivos de instalación. # Instalamos iputils-arping para hacer uso del comando arping. Es necesario para configurar una IP Virtual. # Descargamos la última versión de ZPM (o IPM, incluida en las versiones a partir de la 2023.1). RUN mkdir /opt/demo && \ chown ${ISC_PACKAGE_MGRUSER}:${ISC_PACKAGE_IRISGROUP} /opt/demo && \ chmod 666 /usr/irissys/mgr/iris.key && \ apt-get update && apt-get install iputils-arping gettext-base && \ wget -O /opt/demo/zpm.xml https://pm.community.intersystems.com/packages/zpm/latest/installer USER ${ISC_PACKAGE_MGRUSER} WORKDIR /opt/demo # Configuramos el rol del mirror por defecto a master. # Se sobreescribirá en el archivo docker-compose en el momento de la ejecución (master para la primera instancia, backup, y report) ARG IRIS_MIRROR_ROLE=master # Copiamos el contenido del directorio de archivos de configuración en /opt/demo. # Únicamente hemos creado una configuración simple de nuestra base de datos y los mapeos de globales. # Posteriormente en este mismo artículo incluiremos otros archivos de configuración para desplegar el mirror. ADD config-files . SHELL [ "/session.sh" ] # Instalamos el ZPM (no será necesario para versiones a partir de 2023.1) # Usamos ZPM para instalar config-api # Cargamos el archivo de configuración simple-config.json con config-api para: # - crear la base de datos "myappdata", # - añadirmos un mapeo de globales en el namespace "USER" para los globales "demo.*" a la base de datos "myappdata". # Basicamente, el punto de entrada para instalar tu aplicación de ObjectScript es este. # Para este ejemplo cargaremos simple-config.json para crear una base de datos simple y un mapeo de globals. RUN \ Do $SYSTEM.OBJ.Load("/opt/demo/zpm.xml", "ck") \ zpm "install config-api" \ Set sc = ##class(Api.Config.Services.Loader).Load("/opt/demo/simple-config.json") # Copiamos el script de arranque del mirror. COPY init_mirror.sh / ``` ### Creación de la imagen IRIS El Dockerfile está listo, podemos crear la imagen: ``` docker build --no-cache --tag mirror-demo:latest . ``` Esta imagen se utilizará para ejecutar los nodos primarios, los de copias de seguridad y los de informes. ### El archivo .env Los archivos de configuración JSON y docker-compose utilizan variables de entorno. Sus valores se almacenan en un archivo llamado `.env`. Para este ejemplo, nuestro archivo env es: ``` APP_NET_SUBNET=172.16.238.0/24 MIRROR_NET_SUBNET=172.16.220.0/24 IRIS_HOST=172.16.238.100 IRIS_PORT=1972 IRIS_VIRTUAL_IP=172.16.238.100 ARBITER_IP=172.16.238.10 MASTER_APP_NET_IP=172.16.238.20 MASTER_MIRROR_NET_IP=172.16.220.20 BACKUP_APP_NET_IP=172.16.238.30 BACKUP_MIRROR_NET_IP=172.16.220.30 REPORT_APP_NET_IP=172.16.238.40 REPORT_MIRROR_NET_IP=172.16.220.40 ``` ### Preparación del archivo de configuración del primer miembro del mirror La librería config-api permite configurar un *mirror*, por lo que debemos crear un archivo de configuración específico para el primer miembro *mirror* `config-files/mirror-master.json` Para mayor comodidad, los comentarios se sitúan directamente en el JSON. Podéis descargar el [mirror-master.json sin comentarios aquí](https://raw.githubusercontent.com/lscalese/iris-mirroring-samples/master/config-files/mirror-master.json). ```json { "Security.Services" : { "%Service_Mirror" : { "Enabled" : true } }, "SYS.MirrorMaster" : { "Demo" : { "Config" : { "Name" : "Demo", /* El nombre de nuestro mirror */ "SystemName" : "master", /* El nombre de esta instancia en el mirror */ "UseSSL" : true, "ArbiterNode" : "${ARBITER_IP}|2188", /* Dirección IP y puerto para el nodo del arbiter */ "VirtualAddress" : "${IRIS_VIRTUAL_IP}/24", /* Dirección IP Virtual IP */ "VirtualAddressInterface" : "eth0", /* Interfaz de red usada para la dirección IP Virtual. */ "MirrorAddress": "${MASTER_MIRROR_NET_IP}", /* Dirección IP de este nodo en la red privada del mirror */ "AgentAddress": "${MASTER_APP_NET_IP}" /* Dirección IP de este nodo (Agent está instalado en la misma máquina) */ }, "Databases" : [{ /* Lista de bases de datos añadidas al mirror */ "Directory" : "/usr/irissys/mgr/myappdata/", "MirrorDBName" : "MYAPPDATA" }], "SSLInfo" : { /* Configuración SSL */ "CAFile" : "/certificates/CA_Server.cer", "CertificateFile" : "/certificates/master_server.cer", "PrivateKeyFile" : "/certificates/master_server.key", "PrivateKeyPassword" : "", "PrivateKeyType" : "2" } } } } ``` ### Preparación del archivo de configuración del miembro failover Creamos un archivo de configuración para los miembros de backup de respaldo (failover) `config-files/mirror-backup.json`. Se parece al primer miembro del mirror: ```json { "Security.Services" : { "%Service_Mirror" : { "Enabled" : true } }, "SYS.MirrorFailOver" : { "Demo" : { /* Datos del mirror al que se va a unir */ "Config": { "Name" : "Demo", "SystemName" : "backup", /* Nombre de esta instancia en el mirror */ "InstanceName" : "IRIS", /* Nombre de la instancia de IRIS del primer miembro del mirror */ "AgentAddress" : "${MASTER_APP_NET_IP}", /* Dirección IP del Agent del primer miembro del mirror */ "AgentPort" : "2188", "AsyncMember" : false, "AsyncMemberType" : "" }, "Databases" : [{ /* Base de datos en mirror */ "Directory" : "/usr/irissys/mgr/myappdata/" }], "LocalInfo" : { "VirtualAddressInterface" : "eth0", /* Interfaz de red usada por la dirección IP Virtual. */ "MirrorAddress": "${BACKUP_MIRROR_NET_IP}" /* Dirección IP de este nodo en la red privada del mirror */ }, "SSLInfo" : { "CAFile" : "/certificates/CA_Server.cer", "CertificateFile" : "/certificates/backup_server.cer", "PrivateKeyFile" : "/certificates/backup_server.key", "PrivateKeyPassword" : "", "PrivateKeyType" : "2" } } } } ``` ### Preparación del archivo de configuración del miembro en modo lectura-escritura asíncrono Es bastante similar al archivo de configuración de failover. Las diferencias son los valores de `AsyncMember`, `AsyncMemberType`, y `MirrorAddress`. Creamos el archivo `./config-files/mirror-report.json`: ```json { "Security.Services" : { "%Service_Mirror" : { "Enabled" : true } }, "SYS.MirrorFailOver" : { "Demo" : { "Config": { "Name" : "Demo", "SystemName" : "report", "InstanceName" : "IRIS", "AgentAddress" : "${MASTER_APP_NET_IP}", "AgentPort" : "2188", "AsyncMember" : true, "AsyncMemberType" : "rw" }, "Databases" : [{ "Directory" : "/usr/irissys/mgr/myappdata/" }], "LocalInfo" : { "VirtualAddressInterface" : "eth0", "MirrorAddress": "${REPORT_MIRROR_NET_IP}" }, "SSLInfo" : { "CAFile" : "/certificates/CA_Server.cer", "CertificateFile" : "/certificates/report_server.cer", "PrivateKeyFile" : "/certificates/report_server.key", "PrivateKeyPassword" : "", "PrivateKeyType" : "2" } } } } ``` ### Generación de certificados y configuración de los nodos IRIS y ¡Todos los archivos de configuración están listos! Ahora tenemos que añadir un *script* para generar certificados para asegurar la comunicación entre cada uno de los nodos. En el repositorio [gen-certificates.sh](https://raw.githubusercontent.com/lscalese/iris-mirroring-samples/master/gen-certificates.sh) hay un script listo para ser usado ``` # sudo es obligatorio debido al uso de chown, chgrp chmod. sudo ./gen-certificates.sh ``` Para configurar cada nodo `init_mirror.sh` se realizará al iniciar los contenedores. Se configurará posteriormente en `docker-compose.yml` en la sección de comandos `command: ["-a", "/init_mirror.sh"]` : ```bash #!/bin/bash # Base de datos usada para probar el mirror. DATABASE=/usr/irissys/mgr/myappdata # Directorio que contiene myappdata copiada por el master para restaurar en los otros nodos y hacer el mirror. BACKUP_FOLDER=/opt/backup # Archivo de configuración del mirror en json con formato de config-api para el nodo master. MASTER_CONFIG=/opt/demo/mirror-master.json # Archivo de configuración del mirror en json con formato de config-api para el nodo de backup. BACKUP_CONFIG=/opt/demo/mirror-backup.json # Archivo de configuración del mirror en json con formato de config-api para el nodo asíncrono. REPORT_CONFIG=/opt/demo/mirror-report.json # El nombre del mirror... MIRROR_NAME=DEMO # Lista de miembros del mirror. MIRROR_MEMBERS=BACKUP,REPORT # Ejecutado en el master. # Carga de la configuración del mirror usando config-api con el archivo /opt/demo/simple-config.json. # Iniciamos un Job para auto-aceptar otros miembros llamados "backup" y "report" se unan al mirror (evitando la validación manual desde el portal de gestión). master() { rm -rf $BACKUP_FOLDER/IRIS.DAT envsubst < ${MASTER_CONFIG} > ${MASTER_CONFIG}.resolved iris session $ISC_PACKAGE_INSTANCENAME -U %SYS
Artículo
Alberto Fuentes · 28 abr, 2021

Consejos para depurar con %Status

## Introducción Si resuelves problemas complejos en ObjectScript, probablemente tienes mucho código que funciona con los valores de %Status. Si has interactuado con clases persistentes desde una perspectiva de objetos (%Save, %OpenId, etc.), casi seguro que las ha visto.  Un %Status proporciona una envoltura alrededor de un mensaje de error localizable en las plataformas de InterSystems. Un estado OK (`$$$OK`) simplemente es igual a 1, mientras que un mal estado (`$$$ERROR(errorcode,arguments...)`) se representa como un 0 seguido de un espacio seguido de una lista `$ListBuild` con información estructurada sobre el error. [$System.Status (mira la referencia de clase)](https://docs.intersystems.com/irislatest/csp/documatic/%25CSP.Documatic.cls?PAGE=CLASS&LIBRARY=%25SYS&CLASSNAME=%25SYSTEM.Status) proporciona varias APIs útiles para trabajar con los valores de %Status; la referencia de clase es útil y no os molestaré duplicándola aquí. También ha habido algunos otros artículos/preguntas útiles sobre el tema (consulta los enlaces al final de esta publicación). En este artículo me centraré en algunas técnicas de depuración en vez de escribir las prácticas recomendadas (de nuevo, si las estás buscando, consulta los enlaces al final). ## Ejemplo de código motivador Nota: **¡nunca escribas un código como este!** :) Revisa siempre tus estados y devuélvelos/lánzalos como excepciones (por ejemplo, `$$$ThrowStatus(someErrorStatus)`) y hará que la depuración sea MUY sencilla. Class DC.Demo.MaskedErrorStatus Extends %Persistent { Property Answer As %TinyInt; ClassMethod Run() As %Status {     Set instance = ..%New()     Set instance.Answer = 9000     Do instance.%Save()          Set instance = ..%OpenId(1,,.sc)     Set instance.Answer = 42     Do instance.%Save()          Quit $$$OK } } Cuando se ejecuta desde el terminal, se lanza una excepción; es evidente que algo salió mal. USER>d ##class(DC.Demo.MaskedErrorStatus).Run()    Set instance.Answer = 42  ^ <INVALID OREF>zRun+5^DC.Demo.MaskedErrorStatus.1 ## Truco #1 de depuración con %Status: $System.OBJ.DisplayError() Siempre puedes ejecutar `$System.OBJ.DisplayError()` para imprimir el último estado de error que se creó. Esto funciona porque cada vez que se crea un estado de error (por ejemplo, por medio de $System.Status.Error), la variable `%objlasterror` se establece en ese estado. También puedes utilizar `zwrite %objlasterror` (de forma equivalente). En el caso anterior: USER 2d1>d $system.OBJ.DisplayError() ERROR #5809: Object to Load not found, class 'DC.Demo.MaskedErrorStatus', ID '1' ## Truco #2 de depuración con %Status: seguimiento de stacks Dentro de cada %Status hay un seguimiento del stack (pila de llamadas) en el que se creó el error. Se puede ver esto al utilizar el estado zwrite: USER 2d1>zw %objlasterror %objlasterror="0 "_$lb($lb(5809,"DC.Demo.MaskedErrorStatus","1",,,,,,,$lb(,"USER",$lb("e^%LoadData+18^DC.Demo.MaskedErrorStatus.1^1","e^%Open+16^%Library.Persistent.1^1","e^%OpenId+1^%Library.Persistent.1^1","e^zRun+4^DC.Demo.MaskedErrorStatus.1^1","d^^^0"))))/* ERROR #5809: Object to Load not found, class 'DC.Demo.MaskedErrorStatus', ID '1' */ ¿Quieres ver el seguimiento de stacks en el texto de error (que es más sencillo de utilizar) para cada estado (por ejemplo, utilizando `$System.OBJ.DisplayError()` o `$System.Status.GetErrorText(someStatus))`? Puedes hacerlo al establecer `%oddENV("callererrorinfo",$namespace)' a valores 1 o 2. Puedes ver el efecto aquí: USER>set ^%oddENV("callererrorinfo",$namespace)=1 USER>d $system.OBJ.DisplayError() ERROR #5809: Object to Load not found, class 'DC.Demo.MaskedErrorStatus', ID '1' [%LoadData+18^DC.Demo.MaskedErrorStatus.1:USER] USER>set ^%oddENV("callererrorinfo",$namespace)=2 USER>d $system.OBJ.DisplayError() ERROR #5809: Object to Load not found, class 'DC.Demo.MaskedErrorStatus', ID '1' [e^%LoadData+18^DC.Demo.MaskedErrorStatus.1^1 e^%Open+16^%Library.Persistent.1^1 e^%OpenId+1^%Library.Persistent.1^1 e^zRun+4^DC.Demo.MaskedErrorStatus.1^1 d^^^0:USER] USER>k ^%oddENV("callererrorinfo",$namespace) USER>d $system.OBJ.DisplayError() ERROR #5809: Object to Load not found, class 'DC.Demo.MaskedErrorStatus', ID '1' Ten en cuenta que esto sólo es apropiado en un entorno de desarrollo - no querrás que tus usuarios vean el interior de tu código -. En realidad, es mejor evitar mostrar los valores de %Status directamente a los usuarios. Son preferibles los mensajes de error específicos de la aplicación, más fáciles de utilizar. Pero ese es un tema para otro día. ## Truco #3 de depuración con %Status: el elegante zbreak Aquí es donde se pone difícil - en el caso de este fragmento de código, la causa raíz es un %Status no verificado de `%Save()` que antes estaba en el fragmento de código. Es fácil imaginar un ejemplo mucho más complicado en el que encontrar lo que ha fallado es realmente difícil, especialmente si se trata de un error que se produce en algún punto del código de la plataforma. Mi método preferido para gestionar esto, sin saltar a un depurador interactivo, es utilizar un comando [zbreak](https://cedocs.intersystems.com/latest/csp/docbook/DocBook.UI.Page.cls?KEY=TCOS_ZBreak) en el terminal: USER>zbreak *%objlasterror:"N":"$d(%objlasterror)#2":"set ^mtemptl($i(^mtemptl))=%objlasterror" ...¿qué quiere decir eso? zbreak <cada vez que %objlasterror cambie>:<no haga nada en el depurador mismo>:<mientras %objlasterror esté definido y tenga un valor (por ejemplo, no ha pasado de estar definido a estar indefinido)>:<ejecuta el código para establecer el siguiente subíndice de un global con un subíndice entero que no está en journal (porque comienza con mtemp, en caso de que estemos en una transacción cuando se crea %Status y se haya revertido para cuando observemos el registro; también, con mis iniciales como parte del global para que si alguien lo encuentra en el código comprometido o en una base de datos inflada sepa que debe llamarme) al estado de error> Nota adicional sobre zbreak: puedes ver los breakpoints/watchpoints definidos actualmente si ejecutas "zbreak" sin argumentos, y puedes/debes desactivar estos breakpoints cuando hayas terminado con ellos ejecutando break "off", por ejemplo: USER>zbreak BREAK: No breakpoints %objlasterror F:E S:0 C:"$d(%objlasterror)#2" E:"set ^mtemptl($i(^mtemptl))=%objlasterror" USER>break "off" USER>zbreak BREAK: No breakpoints No watchpoints Entonces, ¿qué sucede cuando el método problemático se ejecuta con el watchpoint establecido? USER>zbreak *%objlasterror:"N":"$d(%objlasterror)#2":"set ^mtemptl($i(^mtemptl))=%objlasterror" USER>d ##class(DC.Demo.MaskedErrorStatus).Run() Set instance.Answer = 42 ^ <INVALID OREF>zRun+5^DC.Demo.MaskedErrorStatus.1 USER 2d1>zw ^mtemptl ^mtemptl=6 ^mtemptl(1)="0 "_$lb($lb(7203,9000,127,,,,,,,$lb(,"USER",$lb("e^zAnswerIsValid+1^DC.Demo.MaskedErrorStatus.1^1","e^%ValidateObject+3^DC.Demo.MaskedErrorStatus.1^4","e^%SerializeObject+3^%Library.Persistent.1^1","e^%Save+4^%Library.Persistent.1^2","d^zRun+3^DC.Demo.MaskedErrorStatus.1^1","d^^^0"))))/* ERROR #7203: Datatype value '9000' greater than MAXVAL allowed of 127 */ ^mtemptl(2)="0 "_$lb($lb(7203,9000,127,,,,,,,$lb(,"USER",$lb("e^zAnswerIsValid+1^DC.Demo.MaskedErrorStatus.1^1","e^%ValidateObject+3^DC.Demo.MaskedErrorStatus.1^4","e^%SerializeObject+3^%Library.Persistent.1^1","e^%Save+4^%Library.Persistent.1^2","d^zRun+3^DC.Demo.MaskedErrorStatus.1^1","d^^^0")),"0 "_$lb($lb(5802,"DC.Demo.MaskedErrorStatus:Answer",9000,,,,,,,$lb(,"USER",$lb("e^EmbedErr+1^%occSystem^1"))))))/* ERROR #7203: Datatype value '9000' greater than MAXVAL allowed of 127- > ERROR #5802: Datatype validation failed on property 'DC.Demo.MaskedErrorStatus:Answer', with value equal to "9000" */ ^mtemptl(3)="0 "_$lb($lb(7203,9000,127,,,,,,,$lb("zAnswerIsValid+1^DC.Demo.MaskedErrorStatus.1","USER",$lb("e^zAnswerIsValid+1^DC.Demo.MaskedErrorStatus.1^1","e^%ValidateObject+3^DC.Demo.MaskedErrorStatus.1^4","e^%SerializeObject+3^%Library.Persistent.1^1","e^%Save+4^%Library.Persistent.1^2","d^zRun+3^DC.Demo.MaskedErrorStatus.1^1","d^^^0"))))/* ERROR #7203: Datatype value '9000' greater than MAXVAL allowed of 127 */ ^mtemptl(4)="0 "_$lb($lb(5802,"DC.Demo.MaskedErrorStatus:Answer",9000,,,,,,,$lb("EmbedErr+1^%occSystem","USER",$lb("e^EmbedErr+1^%occSystem^1"))))/* ERROR #5802: Datatype validation failed on property 'DC.Demo.MaskedErrorStatus:Answer', with value equal to "9000" */ ^mtemptl(5)="0 "_$lb($lb(7203,9000,127,,,,,,,$lb("zAnswerIsValid+1^DC.Demo.MaskedErrorStatus.1","USER",$lb("e^zAnswerIsValid+1^DC.Demo.MaskedErrorStatus.1^1","e^%ValidateObject+3^DC.Demo.MaskedErrorStatus.1^4","e^%SerializeObject+3^%Library.Persistent.1^1","e^%Save+4^%Library.Persistent.1^2","d^zRun+3^DC.Demo.MaskedErrorStatus.1^1","d^^^0")),"0 "_$lb($lb(5802,"DC.Demo.MaskedErrorStatus:Answer",9000,,,,,,,$lb("EmbedErr+1^%occSystem","USER",$lb("e^EmbedErr+1^%occSystem^1"))))))/* ERROR #7203: Datatype value '9000' greater than MAXVAL allowed of 127- > ERROR #5802: Datatype validation failed on property 'DC.Demo.MaskedErrorStatus:Answer', with value equal to "9000" */ ^mtemptl(6)="0 "_$lb($lb(5809,"DC.Demo.MaskedErrorStatus","1",,,,,,,$lb(,"USER",$lb("e^%LoadData+18^DC.Demo.MaskedErrorStatus.1^1","e^%Open+16^%Library.Persistent.1^1","e^%OpenId+1^%Library.Persistent.1^1","e^zRun+4^DC.Demo.MaskedErrorStatus.1^1","d^^^0"))))/* ERROR #5809: Object to Load not found, class 'DC.Demo.MaskedErrorStatus', ID '1' */ Hay un poco de ruido ahí, pero el problema clave salta a la vista: /\* ERROR #7203: Datatype value '9000' greater than MAXVAL allowed of 127 \*/ ¡Debería haber sabido que no debería utilizar %TinyInt! (Y, lo que es más importante, siempre debes verificar los valores de %Status devueltos por los métodos que llamas). ## Lecturas relacionadas [Mis patrones de codificación preferidos para la gestión y reporte de errores](https://community.intersystems.com/post/try-catch-block-i-usually-use-intersystems-objectscript#comment-7751) [%Status frente a otros valores de retorno en los métodos ObjectScript de Caché](https://community.intersystems.com/post/status-vs-other-return-values-cach%C3%A9-objectscript-methods) [Sobre %objlasterror](https://community.intersystems.com/post/about-objlasterror) [Cómo configurar $$$envCallerErrorInfoGet](https://community.intersystems.com/post/how-set-envcallererrorinfoget-windows-get-location-information-within-exception#comment-95586) [Fragmentos de código para gestión de errores de ObjectScript](https://community.intersystems.com/post/objectscript-error-handling-snippets) [Comando ZBREAK](https://cedocs.intersystems.com/latest/csp/docbook/DocBook.UI.Page.cls?KEY=TCOS_ZBreak) Este artículo está etiquetado como "Mejores prácticas" ("Best practices"). Los artículos con la etiqueta "Mejores prácticas" incluyen recomendaciones sobre cómo desarrollar, probar, implementar y administrar mejor las soluciones de InterSystems.
Artículo
Mario Sanchez Macias · 13 mayo, 2021

Chequeando la integridad de datos: Acelerarla o Ralentizarla.

Aunque la [integridad](https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=GCDI_integrity) de las bases de datos Caché e InterSystems IRIS está completamente protegida de las consecuencias de un fallo de sistema, los dispositivos de almacenamiento físico sí que pueden fallar, corrompiendo los datos que almacenan. Por esa razón, muchos sitios optan por realizar chequeos o verificaciones periódicas de integridad de bases de datos, sobre todo en coordinación con las copias de seguridad, para validar que se pueda confiar en una determinada copia de seguridad en caso de que ocurra algún desastre. El chequeo de integridad también puede ser muy necesario para el administrador del sistema, como respuesta a un desastre que implique la corrupción del almacenamiento. El chequeo de integridad ha de leer todos los bloques de los *globals* que están en el proceso de verificación (si actualmente no están en los *buffers*), y en el orden dictado por la estructura del global. Esto lleva mucho tiempo, **pero el chequeo de integridad es capaz de leer tan rápido como el subsistema de almacenamiento pueda soportar**.  En algunas situaciones, es aconsejable ejecutarlo de esta manera para obtener resultados lo más rápido posible. En otras situaciones, el chequeo de integridad debe ser más conservador para evitar consumir demasiado ancho de banda del subsistema de almacenamiento.  ## Plan de Ataque El siguiente esquema se adapta a la mayoría de las situaciones. El análisis detallado del resto de este artículo proporciona la información necesaria para actuar sobre cualquiera de ellos, o para derivar otras líneas de acción.  1. Si utilizas Linux y el chequeo de integridad es lento, consulta la información que encontrarás más abajo sobre la activación de la E/S asíncrona.  2. Si el chequeo de integridad debe completarse lo más rápido posible (ejecutándose en un entorno aislado, o porque los resultados se necesitan urgentemente), utiliza el chequeo de Integridad Multiproceso para comprobar varios *globals* o bases de datos en paralelo. El número de procesos multiplicado por el número de lecturas asíncronas simultáneas que cada proceso realizará (8 de forma predeterminada, o 1 si se utiliza Linux con la E/S asíncrona deshabilitada) es el límite del número de lecturas simultáneas sostenidas. Considera que el promedio puede ser la mitad de eso y después compara con las características del subsistema de almacenamiento.  Por ejemplo, con el almacenamiento dividido en 20 unidades y las 8 lecturas simultáneas predeterminadas por proceso, pueden ser necesarios cinco o más procesos para capturar toda la capacidad del subsistema de almacenamiento (5*8/2=20). 3. Al equilibrar la velocidad del chequeo de integridad contra su impacto en producción, primero ajusta el número de procesos en el chequeo de integridad multiproceso; después, si es necesario, consulta el ajuste SetAsyncReadBuffers. Consulta el chequeo de Integridad de Aislamiento indicado más abajo para obtener una solución a largo plazo (y para eliminar falsos positivos). 4. Si ya está limitado a un solo proceso (por ejemplo, hay un *global* extremadamente grande u otras restricciones externas) y la velocidad de el chequeo de integridad necesita un ajuste hacia arriba o hacia abajo, consulta más abajo el ajuste SetAsyncReadBuffers. ## Chequeo de Integridad Multiproceso La solución general para obtener un chequeo de integridad que se complete más rápido (usando recursos del sistema a un mayor ritmo) es dividir el trabajo entre varios procesos paralelos. Algunas de las interfaces de usuario y APIs de chequeo de integridad lo hacen, mientras que otras utilizan un solo proceso. La asignación a los procesos es una por global, por lo que el chequeo de un único *global* siempre se realiza mediante un solo proceso (las versiones anteriores a Caché 2018.1 dividían el trabajo por base de datos en vez de por *global*). La API principal para verificar la integridad de varios procesos es **CheckLIst Integrity** (consulta [la documentación](https://docs.intersystems.com/irislatest/csp/docbook/Doc.View.cls?KEY=GCDI_integrity#GCDI_integrity_verify_utility) para más detalles). Recopila los resultados en un *global* temporal para ser mostrados por Display^Integrity. El siguiente es un ejemplo de verificación de tres bases de datos usando cinco procesos. Si se omite el parámetro de la lista de bases de datos, se verifican todas las bases de datos. set dblist=$listbuild(“/data/db1/”,”/data/db2/”,”/data/db3/”) set sc=$$CheckList^Integrity(,dblist,,,5) do Display^Integrity() kill ^IRIS.TempIntegrityOutput(+$job) /* Note: evaluating ‘sc’ above isn’t needed just to display the results, but...    $system.Status.IsOK(sc) - ran successfully and found no errors    $system.Status.GetErrorCodes(sc)=$$$ERRORCODE($$$IntegrityCheckErrors) // 267                            - ran successfully, but found errors.    Else - a problem may have prevented some portion from running, ‘sc’ may have            multiple error codes, one of which may be $$$IntegrityCheckErrors. */ El uso de CheckLIst^Integrity de esta manera es la forma más sencilla de lograr el nivel de control que nos interesa. Tanto la interfaz del Portal de administración como la Tarea de Chequeo de Integridad (incorporada, pero no programada) utilizan varios procesos, pero puede que no ofrezca un control suficiente para nuestros propósitos.* Otras interfaces de chequeo de integridad, especialmente la interfaz de usuario del terminal, ^INTEGRIT o ^Integrity, así como Silent^Integrity, realizan el chequeo de integridad en un solo proceso. Por lo tanto, estas interfaces no completan el chequeo tan rápido como es posible conseguir, y utilizan menos recursos. Sin embargo, una ventaja es que sus resultados son visibles, se registran en un archivo o se envían al terminal, según se verifica cada *global*, y en un orden bien definido. ## E/S asíncronas Un proceso de chequeo de integridad recorre cada bloque puntero de un *global*, uno cada vez, validando cada uno contra el contenido de los bloques de datos a los que apunta. Los bloques de datos se leen con E/S asíncrona para mantener un número de solicitudes de lectura sostenidos para que el subsistema de almacenamiento las procese, y la validación se va completando cada lectura.  En Linux, la E/S asíncrona solo es efectiva en combinación con la E/S directa, que no está habilitada de forma predeterminada hasta InterSystems IRIS 2020.3. Esto explica un gran número de casos en los que el chequeo de la integridad tarda demasiado tiempo en Linux. Afortunadamente, se puede habilitar en Cache 2018.1, IRIS 2019.1 y posteriores, al establecer **wduseasyncio=1** en la sección [config] del archivo .cpf y reiniciando. Este parámetro se recomienda en general para la escalabilidad de E/S en sistemas con mucha carga y es el predeterminado en plataformas que no son de Linux desde Caché 2015.2. Antes de habilitarlo, asegúrate de que has configurado suficiente memoria de caché de base de datos (global *buffers*) porque con Direct E/S, las bases de datos ya no serán almacenadas en la caché (de forma redundante) por Linux. Si no se activan, las lecturas realizadas por el chequeo de integridad se completan de forma sincróna y no se puede utilizar el almacenamiento de forma eficiente.  En todas las plataformas, el número de lecturas que un proceso de chequeo activa esta fijado en 8 por defecto. Si tienes que alterar la velocidad a la que un solo proceso de chequeo lee del disco, este parámetro se puede ajustar: hacia arriba para conseguir que un solo proceso se complete más rápido o hacia abajo para utilizar menos ancho de banda de almacenamiento. Ten en cuenta que: * Este parámetro se aplica a cada proceso de chequeo de integridad. Cuando se utilizan varios procesos, el número de procesos multiplica este número de lecturas sostenidas. Cambiar el número de procesos de chequeo de integridad paralelos tiene un impacto mucho mayor y, por tanto, normalmente es lo primero que se hace. Cada proceso también está limitado por el tiempo computacional (entre otras cosas), por lo que aumentar el valor de este parámetro tiene un beneficio limitado. * Esto solo funciona dentro de la capacidad del subsistema de almacenamiento para procesar lecturas simultáneas. Valores más altos no tienen ningún beneficio si las bases de datos se almacenan en una sola unidad local, mientras que una matriz de almacenamiento con striping a lo largo de docenas de unidades, puede procesar docenas de lecturas de forma simultánea. Para ajustar este parámetro desde el namespace %SYS, **do SetAsyncReadBuffers^Integrity(**value**)**. Para ver el valor actual, **write $$GetAsyncReadBuffers^Integrity()**. El cambio tiene efecto cuando se verifica el siguiente *global*. La configuración no persiste tras un reinicio del sistema, aunque se puede añadir a SYSTEM^%ZSTART. Hay un parámetro similar para controlar el tamaño máximo de cada lectura cuando los bloques son contiguos (o cercanos) en el disco. Este parámetro se necesita con menos frecuencia, aunque los sistemas con alta latencia de almacenamiento o bases de datos con tamaños de bloque más grandes podrían beneficiarse de un ajuste más fino. El valor tiene unidades de 64KB, por lo que un valor de 1 es 64KB, 4 es 256KB, etc. 0 (el valor predeterminado) permite que el sistema seleccione automáticamente,y, actualmente selecciona 1 (64KB). La función ^Integrity para este parámetro, paralela a las mencionadas anteriormente, son **SetAsyncReadBufferSize** y **GetAsyncReadBufferSize**. ## Aislamiento del Chequeo de integridad Muchos sitios realizan chequeos periódicos de integridad directamente en el sistema de producción. Esto es lo más sencillo de configurar, pero no es lo ideal. Además de las preocupaciones sobre el impacto de el chequeo de integridad en el ancho de banda de almacenamiento, la actualización simultánea de la base de datos a veces puede conducir a errores de falsos positivos (a pesar de las mitigaciones incorporadas en el algoritmo de verificación). Como resultado, los errores reportados por una chequeo de integridad ejecutado en producción deben ser evaluados y/o verificados de nuevo por un administrador. Con frecuencia, existe una mejor opción. Un snapshot del almacenamiento o una imagen de la copia de seguridad se pueden montar en otro servidor, donde una instancia aislada de Caché o IRIS ejecuta el chequeo de integridad. Esto no solo evita cualquier posibilidad de falsos positivos, sino que si el almacenamiento también se aísla de la producción, el chequeo de integridad se puede ejecutar para utilizar completamente el ancho de banda del almacenamiento y completarse mucho más rápido. Este enfoque encaja bien en el modelo donde el chequeo de integridad se utiliza para validar copias de seguridad; una copia de seguridad validada ratifica de forma efectiva la producción, desde el momento en que se hizo la copia de seguridad. Las plataformas en la nube y de virtualización también pueden facilitar el establecimiento de un entorno aislado utilizable a partir de un snapshot.   * * * *La interfaz del Portal de Gestión, la tarea de Chequeo de integridad y el método IntegrityCheck de SYS.Database, seleccionan un número bastante grande de procesos (igual al número de núcleos de la CPU), sin que exista un control que puede ser necesario en muchas situaciones. El Portal de Gestión y la tarea también hacen un doble chequeo de cualquier global que haya informado de un error, en un intento por identificar falsos positivos, que pueden ser debidos a actualizaciones simultáneas. Este nuevo chequeo va más allá de la mitigación de falsos positivos incorporada en los algoritmos de verificación de integridad, y puede ser molesta en algunas situaciones, debido al tiempo adicional que requiere (el nuevo chequeo se ejecuta en un solo proceso y verifica todo el global). Este comportamiento se podrá modificar en el futuro. Este artículo está etiquetado como "Mejores prácticas" ("Best practices") (Los artículos con la etiqueta "Mejores prácticas" incluyen recomendaciones sobre cómo desarrollar, probar, implementar y administrar mejor las soluciones de InterSystems).
Artículo
Henry Pereira · 26 sep, 2021

¡Luchemos contra las máquinas!

![https://media.giphy.com/media/Nxu57gIbNuYOQ/giphy.gif](https://media.giphy.com/media/Nxu57gIbNuYOQ/giphy.gif) Calma, calma, no estoy promoviendo una guerra contra las máquinas como en las películas de ciencia ficción, para evitar la dominación mundial de Ultron o Skynet. Todavía no, todavía no 🤔 Os invito a retar a las máquinas a través de la creación de un juego muy sencillo usando ObjectScript con Python embebido. Tengo que deciros que me emocioné mucho con la función de Python integrado en InterSystems IRIS. Es increíble el montón de posibilidades que se abren para crear aplicaciones fantásticas. Vamos a construir un juego "tres en raya". Las reglas son bastante sencillas y creo que todo el mundo sabe jugar. Es lo que me salvó del tedio en mi infancia, durante los largos viajes en coche con la familia antes de que los niños tuvieran teléfonos móviles o tabletas. ¡Nada como retar a mis hermanos a jugar unas partidas en el cristal borroso! Así que... ¡abrochaos el cinturón y vámonos! ## Normas Como he comentado, las reglas son bastante simples: - solo 2 jugadores por set - se juega por turnos en una cuadrícula de 3x3 - el jugador humano siempre será la letra X y la computadora la letra O - los jugadores solo podrán poner las letras en los espacios vacíos - el primero en completar una secuencia de 3 letras iguales en horizontal, o en vertical o en diagonal, es el ganador - cuando se ocupen los 9 espacios, será un empate y el final de la partida ![https://media4.giphy.com/media/3oriNKQe0D6uQVjcIM/giphy.gif?cid=790b761123702fb0ddd8e14b01746685cc0059bac0bc66e9&rid=giphy.gif&ct=g](https://media4.giphy.com/media/3oriNKQe0D6uQVjcIM/giphy.gif?cid=790b761123702fb0ddd8e14b01746685cc0059bac0bc66e9&rid=giphy.gif&ct=g) Todo el mecanismo y las reglas lo escribiremos en ObjectScript, el mecanismo del jugador de la máquina se escribirá en Python. ## Vamos a trabajar Controlaremos el tablero en un *global*, en el que cada fila estará en un nodo y cada columna en una pieza. Nuestro primer método es iniciar el tablero, para que sea fácil iniciaré el global ya con los nodos (filas A, B y C) y con las 3 piezas: ``` /// Iniciar un juego nuevo ClassMethod NewGame() As %Status { Set sc = $$$OK Kill ^TicTacToe Set ^TicTacToe("A") = "^^" Set ^TicTacToe("B") = "^^" Set ^TicTacToe("C") = "^^" Return sc } ``` en este momento crearemos un método para añadir las letras en los espacios vacíos, para esto cada jugador dará la ubicación del espacio en el tablero. Cada fila una letra y cada columna un número, para poner la X en el medio, por ejemplo, pasamos B2 y la letra X al método. ``` ClassMethod MakeMove(move As %String, player As %String) As %Boolean { Set $Piece(^TicTacToe($Extract(move,1,1)),"^",$Extract(move,2,2)) = player } ``` Vamos a comprobar si la coordinación es válida, la forma más simple que veo es usando una expresión regular: ``` ClassMethod CheckMoveIsValid(move As %String) As %Boolean { Set regex = ##class(%Regex.Matcher).%New("(A|B|C){1}[0-9]{1}") Set regex.Text = $ZCONVERT(move,"U") Return regex.Locate() } ``` Necesitamos garantizar que el espacio seleccionado está vacío. ``` ClassMethod IsSpaceFree(move As %String) As %Boolean { Quit ($Piece(^TicTacToe($Extract(move,1,1)),"^",$Extract(move,2,2)) = "") } ``` ¡Muy bien! Ahora comprobamos si algún jugador ganó el set o si el juego ya está terminado, para esto creemos el método CheckGameResult. Primero verificamos si hubo algún ganador completando por la horizontal, usaremos una lista con las filas y un simple $ Find resuelve ``` Set lines = $ListBuild("A","B","C") // Check Horizontal For i = 1:1:3 { Set line = ^TicTacToe($List(lines, i)) If (($Find(line,"X^X^X")>0)||($Find(line,"O^O^O")>0)) { Return $Piece(^TicTacToe($List(lines, i)),"^", 1)_" won" } } ``` Con otro *For* comprobamos la vertical ``` For j = 1:1:3 { If (($Piece(^TicTacToe($List(lines, 1)),"^",j)'="") && ($Piece(^TicTacToe($List(lines, 1)),"^",j)=$Piece(^TicTacToe($List(lines, 2)),"^",j)) && ($Piece(^TicTacToe($List(lines, 2)),"^",j)=$Piece(^TicTacToe($List(lines, 3)),"^",j))) { Return $Piece(^TicTacToe($List(lines, 1)),"^",j)_" won" } } ``` para comprobar la diagonal: ``` If (($Piece(^TicTacToe($List(lines, 2)),"^",2)'="") && ( (($Piece(^TicTacToe($List(lines, 1)),"^",1)=$Piece(^TicTacToe($List(lines, 2)),"^",2)) && ($Piece(^TicTacToe($List(lines, 2)),"^",2)=$Piece(^TicTacToe($List(lines, 3)),"^",3)))|| (($Piece(^TicTacToe($List(lines, 1)),"^",3)=$Piece(^TicTacToe($List(lines, 2)),"^",2)) && ($Piece(^TicTacToe($List(lines, 2)),"^",2)=$Piece(^TicTacToe($List(lines, 3)),"^",1))) )) { Return ..WhoWon($Piece(^TicTacToe($List(lines, 2)),"^",2)) } ``` por fin, comprobamos si hubo un empate ``` Set gameStatus = "" For i = 1:1:3 { For j = 1:1:3 { Set:($Piece(^TicTacToe($List(lines, i)),"^",j)="") gameStatus = "Not Done" } } Set:(gameStatus = "") gameStatus = "Draw" ``` ¡Genial! ## Es hora de construir la máquina Vamos a crear a nuestro oponente, necesitamos crear un algoritmo capaz de calcular todos los movimientos disponibles y usar una métrica para saber cuál es el mejor movimiento. Lo ideal es utilizar un algoritmo de decisión llamado MiniMax ([Wikipedia: MiniMax](https://en.wikipedia.org/wiki/Minimax#Minimax_algorithm_with_alternate_moves)) ![https://media3.giphy.com/media/WhTC5v5qQP4yAUvGKz/giphy.gif?cid=ecf05e47cx92yiew8vsig62tjq738xf7hfde0a2ygyfdl0xt&rid=giphy.gif&ct=g](https://media3.giphy.com/media/WhTC5v5qQP4yAUvGKz/giphy.gif?cid=ecf05e47cx92yiew8vsig62tjq738xf7hfde0a2ygyfdl0xt&rid=giphy.gif&ct=g) El algoritmo MiniMax es una regla de decisión utilizada en teoría de juegos, teoría de decisiones e inteligencia artificial. Básicamente, necesitamos saber cómo jugar asumiendo cuáles serán los posibles movimientos del oponente y coger el mejor escenario posible. En detalle, tomamos la escena actual y comprobamos de forma recurrente el resultado del movimiento de cada jugador. En caso de que la máquina gane el juego, puntuamos con +1, en caso de que pierda, puntuamos con -1 y con 0 si empata. Si no es el final del juego, abrimos otro árbol con el estado actual del juego. Después de eso, encontramos la jugada con el valor máximo para la máquina y el mínimo para el oponente. Mira el siguiente gráfico - hay 3 movimientos disponibles: B2, C1 y C3. Al elegir C1 o C3, el oponente tiene la oportunidad de ganar en el siguiente turno, pero si elige B2 no importa el movimiento que elija el oponente, la máquina gana la partida. ![minimaxp](/sites/default/files/inline/images/minimaxp.png) Es como tener la gema del tiempo en nuestras manos e intentar encontrar la mejor línea de tiempo. ![https://pa1.narvii.com/7398/463c11d54d8203aac94cda3c906c40efccf5fd77r1-460-184_hq.gif](https://pa1.narvii.com/7398/463c11d54d8203aac94cda3c906c40efccf5fd77r1-460-184_hq.gif) Convirtiendo a Python ```python ClassMethod ComputerMove() As %String [ Language = python ] { import iris from math import inf as infinity computerLetter = "O" playerLetter = "X" def isBoardFull(board): for i in range(0, 8): if isSpaceFree(board, i): return False return True def makeMove(board, letter, move): board[move] = letter def isWinner(brd, let): # check horizontals if ((brd[0] == brd[1] == brd[2] == let) or \ (brd[3] == brd[4] == brd[5] == let) or \ (brd[6] == brd[7] == brd[8] == let)): return True # check verticals if ((brd[0] == brd[3] == brd[6] == let) or \ (brd[1] == brd[4] == brd[7] == let) or \ (brd[2] == brd[5] == brd[8] == let)): return True # check diagonals if ((brd[0] == brd[4] == brd[8] == let) or \ (brd[2] == brd[4] == brd[6] == let)): return True return False def isSpaceFree(board, move): #Retorna true se o espaco solicitado esta livre no quadro if(board[move] == ''): return True else: return False def copyGameState(board): dupeBoard = [] for i in board: dupeBoard.append(i) return dupeBoard def getBestMove(state, player): done = "Done" if isBoardFull(state) else "" if done == "Done" and isWinner(state, computerLetter): # If Computer won return 1 elif done == "Done" and isWinner(state, playerLetter): # If Human won return -1 elif done == "Done": # Draw condition return 0 # Minimax Algorithm moves = [] empty_cells = [] for i in range(0,9): if state[i] == '': empty_cells.append(i) for empty_cell in empty_cells: move = {} move['index'] = empty_cell new_state = copyGameState(state) makeMove(new_state, player, empty_cell) if player == computerLetter: result = getBestMove(new_state, playerLetter) move['score'] = result else: result = getBestMove(new_state, computerLetter) move['score'] = result moves.append(move) # Find best move best_move = None if player == computerLetter: best = -infinity for move in moves: if move['score'] > best: best = move['score'] best_move = move['index'] else: best = infinity for move in moves: if move['score'] < best: best = move['score'] best_move = move['index'] return best_move lines = ['A', 'B', 'C'] game = [] current_game_state = iris.gref("^TicTacToe") for line in lines: for cell in current_game_state[line].split("^"): game.append(cell) cellNumber = getBestMove(game, computerLetter) next_move = lines[int(cellNumber/3)]+ str(int(cellNumber%3)+1) return next_move } ``` Primero, convierto el *global* en una matriz simple, ignorando columnas y filas, dejándolo plano para facilitar. En cada movimiento analizado llamamos al método copyGameState que, como su nombre indica, copia el estado del juego en ese momento, donde aplicamos el MiniMax. El método getBestMove que se llamará repetidamente hasta que finalice el juego encontrando un ganador o un empate. Primero se mapean los espacios vacíos y verificamos el resultado de cada movimiento, cambiando entre los jugadores. Los resultados se almacenan en *move* ['puntuación'] para, después de comprobar todas las posibilidades, encontrar el mejor movimiento. ¡Espero que os haya divertido! Es posible mejorar la inteligencia usando algoritmos como Alpha-Beta Pruning ([Wikipedia: AlphaBeta Pruning](https://en.wikipedia.org/wiki/Alpha%E2%80%93beta_pruning)) o redes neuronales. ¡Solo tened cuidado de no darle vida a Skynet! ![https://media4.giphy.com/media/mBpthYTk5rfbZvdtIy/giphy.gif?cid=790b761181bf3c36d85a50b84ced8ac3c6c937987b7b0516&rid=giphy.gif&ct=g](https://media4.giphy.com/media/mBpthYTk5rfbZvdtIy/giphy.gif?cid=790b761181bf3c36d85a50b84ced8ac3c6c937987b7b0516&rid=giphy.gif&ct=g) No dudeis en dejar cualquier comentario o pregunta. ¡Esto es todo, amigos! Código fuente completo: [InterSystems Iris version 2021.1.0PYTHON](https://gist.github.com/henryhamon/5be7e2147955bec0f623b718cfd83a9d) Este artículo ha sido etiquetado como "Mejores prácticas" ("Best practices"). Los artículos con la etiqueta "Mejores prácticas" incluyen recomendaciones sobre cómo desarrollar, probar, implementar y administrar mejor las soluciones de InterSystems.
Artículo
Luis Angel Pérez Ramos · 25 mayo, 2023

Descripción de FHIRaaS

# Introducción El propósito de este artículo es ofrecer una visión general de InterSystems IRIS FHIR Accelerator Service (FHIRaaS), motivado por la implementación de la aplicación iris-on-fhir, disponible en OEX y desarrollada para el concurso FHIRaaS. Es un tutorial básico que os guiará en la configuración de una función para la implementación de FHIRaaS, incluyendo una clave API y un servidor OAuth 2.0. También mostraré brevemente una librería para utilizar recursos FHIR a través de FHIRaaS. Por último, en artículos posteriores explicaré algunas características de la aplicación iris-on-fhir. Podéis consultar el código completo en el repositorio github de la aplicación. Este contenido se presentará en una serie de 3 artículos. Este primer artículo parece un poco largo, pero no os preocupéis - es porque coloqué muchas imágenes, para ayudaros con los pasos de la configuración. # FHIRaaS IRIS ya proporciona un entorno API FHIR [integrado en IRIS for Health e IRIS Health Connect](https://www.intersystems.com/fhir/#our-products-support-fhir). Pero, si queréis aprovechar el entorno fiable, seguro y de bajo mantenimiento que pueden ofrecer los servicios en la nube, ahora podéis contar con [InterSystems IRIS FHIR Accelerator Service (FHIRaaS)](https://docs.intersystems.com/components/csp/docbook/Doc.View.cls?KEY=FAS_intro). FHIRaaS es una infraestructura FHIR lista para usarse, basada en servicios en la nube. Solo hay que configurar una implementación y empezar a utilizar la API FHIR en las aplicaciones dondequiera que estén: cliente JS (SMART en FHIR), backend tradicional o aplicaciones sin servidor. Para solicitar una prueba gratuita de FHIRaaS, poneos en contacto con InterSystems. ## Configurar una implementación Después de iniciar sesión, haced clic en el botón “CREATE NEW DEPLOYMENT”. ![](https://raw.githubusercontent.com/diashenrique/iris-on-fhir/master/image/article1/0LIauF5BKe.png) Hay que seguir varios pasos. El primero es seleccionar el tamaño de la implementación. En el momento de escribir este artículo, FHIRaaS solo ofrece una opción. Así que simplemente hay que pulsar el botón “CONTINUE”. ![](https://raw.githubusercontent.com/diashenrique/iris-on-fhir/master/image/article1/SW7lHUh3kY.png) El siguiente paso es elegir el proveedor de servicios en la nube que se va a utilizar. De nuevo, solo había una opción disponible cuando se redactó este artículo: AWS. ![](https://raw.githubusercontent.com/diashenrique/iris-on-fhir/master/image/article1/lDpNEO2zC9.png) La última configuración es tan solo el nombre de la implementación. Hay algunas reglas para este nombre, de las que la interfaz de usuario de FHIRaaS alerta cuando se pone un nombre que no sea válido. Además, tened en cuenta que no se puede cambiar este nombre después de esta configuración. Pulsad el botón “CONTINUE” después de elegir el nombre. ![](https://raw.githubusercontent.com/diashenrique/iris-on-fhir/master/image/article1/E8CCKdytt0.png) Por último, revisad la configuración e iniciad la implementación de FHIRaaS pulsando el botón “CREATE”. ![](https://raw.githubusercontent.com/diashenrique/iris-on-fhir/master/image/article1/21dbwDwCMw.png) Si todo funciona correctamente, recibiréis un agradable mensaje, indicando que vuestra implementación de FHIRaaS se está generando. Esperad unos minutos hasta que finalice este proceso. ![](https://raw.githubusercontent.com/diashenrique/iris-on-fhir/master/image/article1/HYLcwQzfEj.png) Después de unos minutos, vuestra implementación de FHIRaaS está lista para su uso. Simplemente pulsad el botón de implementación y empezad a utilizarlo. ![](https://raw.githubusercontent.com/diashenrique/iris-on-fhir/master/image/article1/IZ8FY5q5mI.png) Después de pulsar el botón de implementación, aparecerá la pestaña “Overview”. Tened en cuenta que hay varias pestañas. En este artículo solo trataremos las pestañas “OAuth 2.0”, “Credentials” y “API Development”. Pero esto solo es debido a un tema de alcance del artículo - las otras no son complicadas en absoluto y las podéis explorar fácilmente. ![](https://raw.githubusercontent.com/diashenrique/iris-on-fhir/master/image/article1/q5eUnTOeBu.png) ## Control de acceso FHIRaaS es compatible con dos formas de controlar el acceso: API Key y OAuth 2.0. Veamos cada una de ellas. ### Claves API (API Key) Las claves API son *tokens* generados por FHIRaaS que permiten que las aplicaciones interactúen con la API sin la interacción del usuario. Esto hace que sea el método ideal para la comunicación entre el servidor FHIRaaS y aplicaciones autorizadas de terceros, como una API de chatbot, por ejemplo. Para crear una clave API, hay que ir a la pestaña “Credentials” y hacer clic en “CREATE API KEY”. ![](https://raw.githubusercontent.com/diashenrique/iris-on-fhir/master/image/article1/0ziV95PjEE.png) Después, hay que elegir un nombre para la clave API y presionar “ADD”. ![](https://raw.githubusercontent.com/diashenrique/iris-on-fhir/master/image/article1/UALIEz359m.png) ¡Genial! ¡Ya está creada la clave API! Copiadla y guardadla en un lugar seguro, ya que ya no podréis volver a acceder a esta información. ![](https://raw.githubusercontent.com/diashenrique/iris-on-fhir/master/image/article1/4xhW0b3JYP.png) Después de creada, una clave API solo se puede eliminar. ![](https://raw.githubusercontent.com/diashenrique/iris-on-fhir/master/image/article1/9rDImo7h6G.png) Para utilizar esta clave API, solo hay que añadir un encabezado x-api-key a una solicitud HTTP. Por ejemplo: ``` curl -X GET "https://fhir.lrwvcusn.static-test-account.isccloud.io/Patient" -H "accept: application/fhir+json" -H "x-api-key: your-apy-key" ``` ### OAuth 2.0 - Crear un servidor OAuth 2.0 y añadir usuarios a él Como comenté anteriormente, una clave API es adecuada cuando se necesita utilizar la API sin la interacción del usuario. Pero cuando se crea una aplicación para los usuarios, OAuth 2.0 y OpenID Connect son actualmente el estándar del sector para la autenticación y la autorización. Solo un comentario: a pesar de que OAuth 2.0 y OpenID Connect se pueden utilizar de forma independiente, es muy habitual ver ambos funcionando uno junto al otro. Así que cuando menciono OAuth 2.0 aquí, me refiero a OAuth 2.0 para la autorización y OpenID connect para la autenticación. Vamos a configurar un servidor OAuth 2.0. Primero, hay que ir a la pestaña “OAuth 2.0” y hacer clic en “CREATE AUTHENTICATION SERVER”. ![](https://raw.githubusercontent.com/diashenrique/iris-on-fhir/master/image/article1/9WdzcWGKZp.png) El siguiente paso es elegir un nombre para el servidor OAuth 2.0 y seleccionar qué proveedor de identidad (IdP) se quiere utilizar. En el momento que escribo este artículo, FHIRaaS solo admite AWS Cognito como IdP. Así que solamente hay que hacer clic en "CREATE". ![](https://raw.githubusercontent.com/diashenrique/iris-on-fhir/master/image/article1/WEiVaUhl7o.png) Si la solicitud se completó correctamente, recibiréis un mensaje como el de la imagen siguiente. Ahora podéis añadir usuarios a vuestro IdP haciendo clic en “ADD USER”. ![](https://raw.githubusercontent.com/diashenrique/iris-on-fhir/master/image/article1/cRn6llnRR4.png) Seréis redirigidos a la pestaña “Credentials”. Para añadir un usuario, pulsad el botón “CREATE USER”. ![](https://raw.githubusercontent.com/diashenrique/iris-on-fhir/master/image/article1/m25cp1qZ6n.png) Introducid el nombre de usuario y la contraseña y haced clic en "CREATE". ![](https://raw.githubusercontent.com/diashenrique/iris-on-fhir/master/image/article1/kK2bESQh8u.png) Si todo se ejecuta correctamente, podréis ver un usuario creado en vuestro IdP. Este usuario ya puede iniciar sesión en las aplicaciones autorizadas por este servidor OAuth 2.0. ![](https://raw.githubusercontent.com/diashenrique/iris-on-fhir/master/image/article1/kYkKtw4z5Z.png) ### OAuth 2.0 - Añadir aplicaciones al servidor OAuth 2.0 Después de crear un servidor OAuth 2.0 y añadir usuarios al mismo, dichos usuarios podrán utilizar las aplicaciones autorizadas por este servidor. Ahora, añadiremos una aplicación al servidor. Primero, hay que ir a la pestaña “OAuth 2.0”, después a la pestaña “Application” y hacer clic en “CREATE APPLICATION”. ![](https://raw.githubusercontent.com/diashenrique/iris-on-fhir/master/image/article1/dWype5WvmQ.png) Después, elegid un nombre para vuestra aplicación en el servidor y el servidor OAuth 2.0 que se acaba de crear. Las URL deben apuntar a vuestra aplicación. “Redirect URL” es la dirección de destino cuando los usuarios inician sesión correctamente. “Logout URL” es la página a la que se redirecciona a los usuarios cuando utilicen el IdP para cerrar sesión. ![](https://raw.githubusercontent.com/diashenrique/iris-on-fhir/master/image/article1/6EkFXqnxhM.png) Durante el desarrollo, podéis redirigir a localhost, pero, por supuesto, para producción tenéis que proporcionar una URL accesible desde Internet. Los pasos finales consisten en elegir los recursos FHIR ("scopes") que los usuarios deben aceptar compartir con la aplicación. Para esta sencilla prueba, se solicitan todos los recursos, pero en las aplicaciones reales, se puede controlar cada recurso FHIR, como si la aplicación solo pudiera leer, solo escribir o ambas opciones. Si los usuarios no están de acuerdo con esta solicitud de autorización, el servidor OAuth 2.0 rechazará el acceso a dichos recursos. Después de configurar correctamente los recursos, haced clic en “CREATE”. ![](https://raw.githubusercontent.com/diashenrique/iris-on-fhir/master/image/article1/P11B258GjR.png) Si todo funciona correctamente, veréis un mensaje en color verde. Ahora, podéis comprobar los parámetros de vuestra nueva aplicación o eliminarla haciendo clic en la casilla de la aplicación. También podéis crear más aplicaciones si lo necesitáis. ![](https://raw.githubusercontent.com/diashenrique/iris-on-fhir/master/image/article1/iG9QBPWndU.png) ## Desarrollo de API Una excelente funcionalidad de FHIRaaS es la pestaña Desarrollo de API. Ofrece un explorador de especificaciones OpenAPI de la API FHIR, lo que permite probar fácilmente todas las funciones de FHIR. Para acceder a ella, haced clic en “API Development”. Cuando se haya cargado, podréis seleccionar qué recurso FHIR queréis explorar. Tened en cuenta que FHIRaaS ofrece la versión R4 para los recursos FHIR. ![](https://raw.githubusercontent.com/diashenrique/iris-on-fhir/master/image/article1/zx7NVHKnFH.png) Después, hay que autenticarse para utilizar la herramienta. Y primero hay que crear una clave API. ![](https://raw.githubusercontent.com/diashenrique/iris-on-fhir/master/image/article1/IxnjaDcllj.gif) Bien, ahora, pongamos a todos los pacientes en esta instancia de FHIRaaS: ![](https://raw.githubusercontent.com/diashenrique/iris-on-fhir/master/image/article1/gVVKWw6OzC.gif) Como podéis ver en la animación anterior, se pueden realizar todas las operaciones CRUD sobre el recurso Patient - lo mismo para el resto de recursos disponibles. Lo bueno de esto es que no es necesario conocer toda la estructura de los recursos para poder realizar operaciones directamente sobre ellos. Por ejemplo, si queréis intentar crear un nuevo paciente, la herramienta ofrece una plantilla para dicho recurso: ![](https://raw.githubusercontent.com/diashenrique/iris-on-fhir/master/image/article1/ib5mlqUBF3.gif) Tenéis la misma funcionalidad para otros recursos FHIR. Al final de la página, la herramienta ofrece una agradable vista de todos los recursos relacionados, en forma de esquemas: ![](https://raw.githubusercontent.com/diashenrique/iris-on-fhir/master/image/article1/hhhQhcTTys.gif) # Conclusión En este artículo repasamos algunos aspectos de FHIRaaS y configuramos una implementación de forma práctica. En el próximo artículo, veremos algunos ejemplos sencillos sobre cómo utilizarlo en aplicaciones.
Artículo
Ricardo Paiva · 9 sep, 2019

Cómo mejorar el rendimiento de SQL en las consultas sobre el rango de fechas

¡Hola desarroladores! ¿Os parece que las consultas sobre el rango de fechas son demasiado lentas? ¿Os parece que el rendimiento de SQL es bajo? ¡Tengo un curioso truco que podría ayudaros a solucionar estos problemas! (¡Los desarrolladores de SQL odian que sepáis estas cosas!)* Si tenéis una clase que guarda los registros de hora cuando se añaden datos, entonces esos datos se ordenarán con vuestros valores IDKEY, es decir, TimeStamp1 < TimeStamp2 si y solo si la condición ID1 < ID2 se cumple para todos los valores ID y TimeStamp en la tabla - entonces podéis utilizar esta información para aumentar el rendimiento de las consultas en relación con los rangos de TimeStamp. Echad un vistazo a la siguiente tabla: Class User.TSOrder extends %Persistent { Property TS as %TimeStamp; Property Data as %String (MAXLEN=100, MINLEN=200); Index TSIdx on TS; Index Extent [type=bitmap, extent]; } Si añadimos 30 000 000 de filas aleatorias con las fechas de los últimos 30 días, se obtendrán 1 000 000 de filas por día. Ahora, si queremos consultar la información de un día específico, hay que escribir lo siguiente: SELECT ID, TS, Data FROM TSOrder WHERE TS >= '2016-07-01 00:00:00.00000' AND TS <= '2016-07-01 23:59:59.999999' Es una consulta razonable. Sin embargo, en mi sistema tomó 2 171 792 referencias globales y 7,2 segundos. Pero si sabemos que los IDs y los TimeStamps están en el mismo orden, podemos utilizar los TimeStamps para obtener un rango de los ID. Mirad la siguiente consulta: SELECT ID, TS, Data FROM TSOrder WHERE ID >= (SELECT TOP 1 ID FROM TSOrder WHERE TS >='2016-07-01 00:00:00.00000' ORDER BY TS ASC) AND ID <= (SELECT TOP 1 ID FROM TSOrder WHERE TS <='2016-07-01 23:59:59.999999' ORDER BY TS DESC) La nueva consulta se completa en 5,1 segundos, ¡y solo necesita 999 985 referencias globales**! Esta técnica puede aplicarse de una manera más práctica a las tablas con más campos indexados y a las consultas que tengan varias condicionales WHERE. El rango del ID que se genera a partir de las subconsultas puede ponerse en un formato de mapa de bits, el cual genera una velocidad increíble cuando se obtiene una solución con varios índices. La tabla Ens.MessageHeader es un excelente ejemplo en la que se podría aplicar este truco. Este es el resultado de un EJEMPLO. Si se tienen muchas sentencias del condicional WHERE en la misma tabla (y están indexadas, ¡obvio!), ¡entonces esta técnica puede ofrecer resultados mucho MEJORES! ¡Probadla en vuestras consultas! * En realidad, los desarrolladores de SQL no odian que sepáis estas cosas, pero si algo nos ha enseñado Internet es que las frases llamativas atraen más tráfico. ** Cuando se prueban consultas que devuelven tantas filas, el SMP no puede gestionarlas, y la mayor parte del tiempo se dedica a mostrar los datos. La manera correcta de probarlas es con los métodos Embedded o Dynamic SQL, comprobando los resultados, pero sin generarlos en función del tiempo, y usando SQL Shell para los recuentos globales. También se puede utilizar las estadísticas de SQL para hacerlo. Este artículo está etiquetado como "Mejores prácticas" ("Best practices") (Los artículos con la etiqueta "Mejores prácticas" incluyen recomendaciones sobre cómo desarrollar, probar, implementar y administrar mejor las soluciones de InterSystems).
Artículo
Javier Lorenzo Mesa · 11 dic, 2020

DeepSee: Bases de datos, Namespaces y Mapeos (Parte 1 de 5)

Estoy pensando en implementar Business Intelligence basada en los datos existentes en mis instancias. ¿Cuál es la mejor manera de configurar mis bases de datos y mi entorno para utilizar DeepSee? En este tutorial se responde esta pregunta mostrando tres ejemplos de la arquitectura que se utiliza en DeepSee. Comenzaremos con un modelo de arquitectura básico y resaltaremos sus limitaciones. El siguiente modelo se recomienda para las aplicaciones de Business Intelligence con una complejidad intermedia, y debería ser suficiente para la mayoría de los casos de uso. Terminaremos el tutorial describiendo cómo mejorar la flexibilidad de la arquitectura para administrar implementaciones avanzadas. Cada ejemplo del tutorial presenta una nueva base de datos y sus correspondientes mapeos globales (global mappings), junto con una discusión sobre por qué y cuándo deben establecerse. Conforme se construye la arquitectura, también se señalarán los beneficios que proporciona mediante diversos ejemplos flexibles. Antes de empezar Servidores principales y analíticos Para hacer que los datos tengan una alta disponibilidad, por lo general, InterSystems recomienda usar "mirrroring" o "shadowing", y basar la implementación de DeepSee en el servidor de mirror/shadow. La máquina que aloja la copia original de los datos se llama "Servidor principal" ("Primary server"), mientras que las máquinas que alojan las copias de los datos y las aplicaciones de Business lntelligence suelen llamarse "Servidores analíticos" ("Analytics servers") o, a veces, "Servidores de informes" ("Reporting servers"). Es muy importante tener Servidores principales y de análisis, ya que esto permitirá evitar problemas de rendimiento en cualquiera de los dos servidores. Puedes consultar la documentación sobre Arquitectura recomendada. Los datos y el código de la aplicación Almacenar los datos y el código fuente en la misma base de datos normalmente funciona bien, pero solo para aplicaciones a pequeña escala. Para aplicaciones más grandes, es recomendable almacenar los datos y el código fuente en dos bases de datos dedicadas, lo que te permitirá compartir el código con todos los namespaces en los que se ejecute DeepSee, mientras se conservan los datos por separado. La base de datos para los datos de origen debe replicarse desde el servidor en producción. Esta base de datos puede ser solamente de lectura, o de lectura y escritura. También es recomendable mantener el "journaling" activado para esta base de datos. Las clases de origen y las aplicaciones personalizadas deben almacenarse en una base de datos dedicada tanto en el servidor de producción como en el de análisis. Ten en cuenta que estas dos bases de datos para el código fuente no necesitan estar sincronizadas o incluso ejecutar la misma versión de Caché. Por lo general, no es necesario tener activado "journaling", siempre y cuando se realicen con frecuencia copias de seguridad del código en otro sitio. En este tutorial tendremos la siguiente configuración. El namespace APP en el servidor de análisis tiene el APP-DATA y el APP-CODE como las bases de datos predeterminadas. La base de datos APP-DATA tiene acceso a los datos que se encuentran en la base de datos de origen (la tabla de la clase de origen y sus datos) del servidor primario. La base de datos APP-CODE almacena el código de Caché (archivos de tipo .cls y .INT) y otros tipos de código personalizado. Esta separación de los datos y el código es una arquitectura típica y permite que el usuario, por ejemplo, implemente de manera eficiente el código de DeepSee y la aplicación personalizada. Cómo ejecutar DeepSee en diferentes namespaces Las implementaciones de la Business Intelligence usando DeepSee con frecuencia se ejecutan desde diferentes namespaces. En esta publicación mostraremos cómo configurar un namespace APP único, pero este mismo procedimiento es válido para todos los namespaces donde se ejecute la aplicación de Business Intelligence. Documentación Es recomendable estar familiarizado con esta página: Cómo establecer la configuración inicial. En esta página se incluye la configuración de las aplicaciones web, cómo colocar los globales de DeepSee en bases de datos separadas y una lista de mapeos alternativos para los globales de DeepSee. * * * En la segunda parte de esta serie de artículos mostraremos cuál es la implementación de un modelo con una arquitectura básica.
Artículo
Ricardo Paiva · 11 ago, 2021

Cómo aprovechar al máximo $Query

Me encontré con un interesante caso de uso de ObjectScript con una solución general que quería compartir. ## Caso de uso: Tengo una matriz JSON (específicamente, en mi caso, una matriz de problemas de *Jira*) que quiero agregar en algunos campos, por ejemplo: categoría, prioridad y tipo de problema. Después quiero combinar los agregados en una lista simple con el total de cada uno de los grupos. Por supuesto, para la agregación, tiene sentido utilizar una matriz local en el formulario: agg(category, priority, type) = total De tal manera que para cada registro en la matriz de entrada simplemente puedo hacer lo siguiente: Do $increment(agg(category, priority, type)) Pero, cuando haya hecho la agregación, quiero conseguir un formulario más fácil sobre el que iterar, como una matriz con subíndices enteros: summary = n summary(1) = $listbuild(total1, category1, priority1, type1) ... summary(n) = $listbuild(totalN, categoryN, priorityN, typeN) ## Solución básica: El enfoque sencillo es simplemente tener tres bucles "For" anidados con $Order, por ejemplo: Set category = "" For {     Set category = $Order(agg(category))     Quit:category=""          Set priority = ""     For {         Set priority = $Order(agg(category,priority))         Quit:priority=""                  Set type = ""         For {             Set type = $Order(agg(category,priority,type),1,total)             Quit:type=""                          Set summary($i(summary)) = $listbuild(total,category,priority,type)         }     } } Esto es lo que empecé a hacer, pero es mucho código, y si tuviera más dimensiones que agregar se volvería difícil de manejar rápidamente. Esto me hizo preguntarme: ¿hay una solución general para conseguir lo mismo? ¡Resulta que sí la hay! ## Mejor solución con $Query: Decidí que usar $query podría ayudar. Ten en cuenta que esta solución asume una capacidad uniforme de los subíndices/valores en toda la matriz local, sucederían cosas extrañas si se vulnerara esta suposición. ClassMethod Flatten(ByRef deep, Output flat) [ PublicList = deep ] {     Set reference = "deep"     For {         Set reference = $query(@reference)         Quit:reference=""         Set value = $listbuild(@reference)         For i=1:1:$qlength(reference) {             Set value = value_$listbuild($qsubscript(reference,i))         }         Set flat($i(flat)) = value     } } Así que el fragmento de código anterior se sustituye por: Do ..Flatten(.agg,.summary) Hay que tener en cuenta algunas cosas sobre esta solución: * _deep_ necesita estar en la PublicList de $query para ser capaz de operar en ella * en cada iteración, _reference_ se cambia para hacer referencia al siguiente conjunto de subíndices que tenga un valor en _deep_, por ejemplo el valor podría ser: _deep("foo","bar")_ * $qlength devuelve el número de subíndices en _reference_ * $qsubscript devuelve el valor del enésimo subíndice de _reference_ * Cuando las listas $listbuild están concatenadas, el resultado es una lista $listbuild válida con las listas combinadas (¡esto es mucho mejor que usar cualquier otro separador!) ## Resumen $query, $qlength y $qsubscript son útiles para lidiar con matrices globales/locales de capacidad arbitraria. ## Lecturas adicionales $Query: https://docs.intersystems.com/irisforhealthlatest/csp/docbook/DocBook.UI.Page.cls?KEY=RCOS_FQUERY $QSubscript: https://docs.intersystems.com/irisforhealthlatest/csp/docbook/Doc.View.cls?KEY=RCOS_fqsubscript $QLength: https://docs.intersystems.com/irisforhealthlatest/csp/docbook/Doc.View.cls?KEY=RCOS_fqlength Este artículo está etiquetado como "Mejores prácticas" ("Best practices"). Los artículos con la etiqueta "Mejores prácticas" incluyen recomendaciones sobre cómo desarrollar, probar, implementar y administrar mejor las soluciones de InterSystems.