Nova postagem

Pesquisar

Anúncio
· jan 4, 2024

ROUND 2 EXTENDED: GenAI Crowdsourcing Mini-Contest by InterSystems Innovation Acceleration Program

Hi Community,

Round 2 of the GenAI Crowdsourcing Mini-Contest has been extended. The new deadline for all investments is tomorrow - Friday, January 5th.

For more information on round 2, see @Alki.Iliopoulou's announcement here.
 

Discussão (0)2
Entre ou crie uma conta para continuar
Artigo
· Dez. 29, 2023 5min de leitura

Performance tests IRIS - PostgreSQL - MySQL using Python

It seems like yesterday when we did a small project in Java to test the performance of IRIS, PostgreSQL and MySQL (you can review the article we wrote back in June at the end of this article). If you remember, IRIS was superior to PostgreSQL and clearly superior to MySQL in insertions, with no big difference in queries.

Well, shortly after @Dmitry Maslennikov told me "Why don't you test it from a Python project?" Well, here is the Python version of the tests we previously performed using the JDBC connections.

First of all, let you know that I am not an expert in Python, so if you see anything that could be improved, do not hesitate to contact me.

For this example I have used Jupyter Notebook, which greatly simplifies Python development and allows us to see step by step what we are doing. Associated with this article you have the application so that you can do your own tests.

Warning for Windows users

If you clone the GitHub project in Visual Studio Code you may have to change the default end of line configuration from CRLF to LF to be able to correctly deploy the containers:

If you are going to try to reproduce the tests on your computers, you must take the following into consideration: Docker Desktop will request permissions to access the folders on your computers that it needs to deploy the project. If you have not configured access permission to these folders before launching the Docker containers, the initial creation of the test table in PostgreSQL will fail, so before launching the project you must configure shared access to the project folders in your DockerDesktop.

To do this you must access Settings -> Resources -> File sharing and add the folder where you have cloned the project to the list:

You are warned!

 

Test of performance

For these tests we will use a fairly simple table with the most basic information possible about a patient. Here you can see the command to create the table in SQL:

CREATE TABLE Test.Patient (
    Name VARCHAR(225),
    Lastname VARCHAR(225),
    Photo VARCHAR(5000),
    Phone VARCHAR(14),
    Address VARCHAR(225)    
)

As you can see, we have defined the patient's photo as a VARCHAR(5000), the reason for this is because here we are going to include (theoretically) the vectorized information of the photo. A few months ago I published an article explaining how using Embedded Python we could implement IRIS, a facial recognition system (here) where you can see how images are transformed into vectors for later comparison. Well, the issue of vectorization comes from the fact that said vector format is the norm in many Machine Learning models and it never hurts to test with something similar to reality (just something).

 

Jupyter Notebook Setup

To simplify the development of the project in Python as much as possible, I have used the magnificent Jupyter Notebook tool that allows us to develop each of the functionalities that we will need step by step.

Here's a look at our Jupyter:

Let's take a look at the most interesting points of it:

Importing libraries:

import iris
import names
import numpy as np
from datetime import datetime
import psycopg2
import mysql.connector
import matplotlib.pyplot as plt
import random_address
from phone_gen import PhoneNumber

Connecting to the databases:

IRIS:

connection_string = "iris:1972/TEST"
username = "superuser"
password = "SYS"
connectionIRIS = iris.connect(connection_string, username, password)
cursorIRIS = connectionIRIS.cursor()
print("Connected")

PostgreSQL:

connectionPostgres = psycopg2.connect(database="testuser",
                        host="postgres",
                        user="testuser",
                        password="testpassword",
                        port="5432")
cursorPostgres = connectionPostgres.cursor()
print("Connected")

MySQL:

connectionMySQL = mysql.connector.connect(
  host="mysql",
  user="testuser",
  password="testpassword"
)
cursorMySQL = connectionMySQL.cursor()
print("Connected")

Generation of the values to be inserted

phone_number = PhoneNumber("USA")
resultsIRIS = []
resultsPostgres = []
resultsMySQL = []
parameters =  []
for x in range(1000):
    rng = np.random.default_rng()
    parameter = []
    parameter.append(names.get_first_name())
    parameter.append(names.get_last_name())
    parameter.append(str(rng.standard_normal(50)))
    parameter.append(phone_number.get_number())
    parameter.append(random_address.real_random_address_by_state('CA')['address1'])
    parameters.append(parameter)

print("Parameters built")

Insertion into IRIS

date_before = datetime.now()

cursorIRIS.executemany("INSERT INTO Test.Patient (Name, Lastname, Photo, Phone, Address) VALUES (?, ?, ?, ?, ?)", parameters)
connectionIRIS.commit()
difference = datetime.now() - date_before
print(difference.total_seconds())
resultsIRIS.append(difference.total_seconds())

Insertion into PostgreSQL

date_before = datetime.now()
    
cursorPostgres.executemany("INSERT INTO test.patient (name, lastname, photo, phone, address) VALUES (%s,%s,%s,%s,%s)", parameters)
connectionPostgres.commit()
difference = datetime.now() - date_before
print(difference.total_seconds())
resultsPostgres.append(difference.total_seconds())

Insertion into MySQL

date_before = datetime.now()
    
cursorMySQL.executemany("INSERT INTO test.patient (name, lastname, photo, phone, address) VALUES (%s,%s,%s,%s,%s)", parameters)
connectionMySQL.commit()
difference = datetime.now() - date_before
print(difference.total_seconds())
resultsMySQL.append(difference.total_seconds())

For our test I have decided to insert the following set of values for each database:

  • 1 insertion with 1000 patients.
  • 1 insertion with 5000 patients.
  • 1 insertion with 20,000 patients.
  • 1 insertion with 50,000 patients.

Keep in mind when performing the tests that the longest time spent is creating the values to be inserted by Python. To bring it closer to reality, I have launched several tests in advance so that the databases already have a significant set of records (around 200,000 records).

Test results

Insertion of 1000 patients:

  • InterSystems IRIS: 0.037949 seconds.
  • PostgreSQL: 0.106508 seconds.
  • MySQL: 0.053338 seconds.

Insertion of 5,000 patients:

  • InterSystems IRIS: 0.162791 seconds.
  • PostgreSQL: 0.432642 seconds.
  • MySQL: 0.18925 seconds.

Insertion of 20,000 patients:

  • InterSystems IRIS: 0.601944 seconds.
  • PostgreSQL: 1.803113 seconds.
  • MySQL: 0.594396 seconds.

Insertion of 50,000 patients:

  • InterSystems IRIS: 1.482824 seconds.
  • PostgreSQL: 4.581251 seconds.
  • MySQL: 2.162996 seconds.

Although this is a fairly simple test, it is very significant since it allows us to see the trend of each database regarding insertion performance.

Conclusions

If we compare the performance in the tests carried out with the Java project and the current one in Python we will see that on this occasion the behavior of PostgreSQL is clearly inferior to the Python project, being 4 times slower than InterSystems IRIS,  while MySQL has improved compared to to the Java version.

Unquestionably InterSystems IRIS remains the best of the three, with more linear behavior and better insertion performance, regardless of the technology used.

Technical characteristics of the laptop used for the tests:

  • Operative System: Microsoft Windows 11 Pro.
  • Processor: 13th Gen Intel(R) Core(TM) i9-13900H, 2600 Mhz.
  • RAM: 64 GB.
6 Comments
Discussão (6)2
Entre ou crie uma conta para continuar
Anúncio
· Dez. 27, 2023

InterSystems Ideas News #10

Hi Developers!

Welcome to Issue 10 of the InterSystems Ideas newsletter! Here's what you can expect from it:

​​​​✓ Month of ideas about "FHIR and Digital Health Interoperability"

✓ Reached 300 ideas on the Ideas Portal 🎉

✓ Recently implemented ideas

 

 Since the new programming contest is dedicated to FHIR and Digital Health Interoperability, we've decided to gather all the related ideas in one place (see the table below). To show your interest and support for your fellow members, we invite everyone to vote for existing ideas or post new ones before the start of the Contest.

Create new ideas devoted to "FHIR and Digital Health Interoperability" and promote your ideas on the Community by creating discussions. The more votes an idea receives, the greater the chances are of its implementation. Vote and comment on existing ideas related to FHIR and Digital Health Interoperability:

 

 When we launched the portal last year, we had very few ideas. A year and a half later, the number of ideas on the portal exceeded 300. 🌟

Thank you so much @Armin Gayl for posting the 300th idea Optimization of the HL7 Schema documentation to the Ideas Portal. 👏

Thanks to all Community members who publish, comment, vote, and implement ideas. Looking forward to your new bright ideas in 2024.👏

 

  To round up this newsletter, here are 4 ideas implemented during the last 2 months.

👏 Thank you for implementing and posting these ideas 👏


Happy New Year! 🎄In 2024 post your brilliant ideas, vote for existing ideas, and comment on them on our InterSystems Ideas Portal!💡

4 Comments
Discussão (4)4
Entre ou crie uma conta para continuar
Artigo
· Dez. 26, 2023 3min de leitura

CSPアプリケーションをReactを使って書き換えるその4

IRIS側の処理は、IRISでREST APIを実装する方法を理解していれば、簡単です。

前回のログイン処理でユーザー認証をIRIS側でどのように実装されているか確認して見ましょう。

まずはディスパッチクラスの定義です。

Shop.Brokerというクラスの中で定義されています。

checkpasswordというメソッドが最後に定義されていて、最終的にShop.Rest.Customer:checkPasswordという(クラス)メソッドが呼ばれているのがわかると思います。

 

ここで定義しているパラメータは、とりあえずおまじない的に含めておくことをお勧めします。

(説明し出すと少し長くなるので)

Class Shop.Broker Extends %CSP.REST
{
Parameter CONVERTINPUTSTREAM = 1;
Parameter HandleCorsRequest = 1;
XData UrlMap
{
<Routes>
  <Route Url="/product/:code" Method="GET" Call="Shop.Rest.Product:getRecord"/>
  <Route Url="/products" Method="GET" Call="Shop.Rest.Product:listRecords"/>
  <Route Url="/deleteproduct/:code" Method="GET" Call="Shop.Rest.Product:deleteRecord"/>
  <Route Url="/addproduct" Method="POST" Call="Shop.Product:createRecord"/>
  <Route Url="/modifyproduct" Method="POST" Call="Shop.Rest.Product:modifyRecord"/>
  <Route Url="/customer/:id" Method="GET" Call="Shop.Rest.Customer:getRecord"/>
  <Route Url="/customers" Method="GET" Call="Shop.Rest.Customer:listRecords"/>
  <Route Url="/deletecustomer/:id" Method="GET" Call="Shop.Rest.Customer:deleteRecord"/>
  <Route Url="/addcustomer" Method="POST" Call="Shop.Customer:createRecord"/>
  <Route Url="/modifycustomer" Method="POST" Call="Shop.Rest.Customer:modifyRecord"/>
  <Route Url="/addorder" Method="POST" Call="Shop.Rest.POrder:createRecord"/>
  <Route Url="/order/:id" Method="GET" Call="Shop.Rest.POrder:getRecord"/>
  <Route Url="/orders" Method="GET" Call="Shop.Rest.POrder:listRecords"/>
  <Route Url="/checkpassword/:userid/:password" Method="GET" Call="Shop.Rest.Customer:checkPassword"/>
</Routes>
}

}

 

 

Shop.Rest.CustomerクラスのcheckPasswordメソッドの中は以下のような感じです。

IRISがわかっている人ならば中身の説明は不要ですよね。

ClassMethod checkPassword(pUserId As %String, pPassword) As %Status
{
    set status = $$$OK
    
    try {
        
      if $data(%request) {
        set %response.ContentType="application/json"
        set %response.CharSet = "utf-8"
      }
      
      set cust=##class(Shop.Customer).CheckPasswd(pUserId, pPassword)
      set return = {}

      if cust = "" {
        set return.authorized = "ng"
      }
      else {
        set return.authorized = "ok"
        set return.ID = cust.%Id()
      }

      do return.%ToJSON()
    
    }
    catch e {
        
      set status = e.AsStatus()
    }
                        
    Quit status
}

 

大体こんな感じです。

結構色々なことを理解しないとなかなか前に進めませんが、やはり理解するには自分で何か作ってみるのが一番です。 

Discussão (0)1
Entre ou crie uma conta para continuar
Artigo
· Dez. 26, 2023 7min de leitura

CSPアプリケーションをReactを使って書き換えるその3

それでは、今回はより具体的にReact開発方法について解説します。

ショップデモのリポジトリの配下にreactというディレクトリがあります。

この下にReactのコードがあります。

ここのreact-setup.mdに記載されている通り、前準備としてreactのテンプレートを作ります。

npx create-react-app shopdemo --template typescript

 

あとはこのReactプロジェクトを動かすためのライブラリのインストールを行います。

詳細は、react-setup.mdに書いてあります。

まず3つのディレクトリがあって、これは絶対こうしなければならないというものでもないのですが、基本的なお作法として用意するのが一般的なようです。

  • public
    • ここにはindex.htmlだけ置くのが一般的なようです。
    • テンプレートが自動生成するものでも良いのですが、Bootstrapを使用する場合は、テンプレートのindex.htmlにそのライブラリのロードを付け加えています。
  • components
    • ここに自分で開発するreactコンポーネントを配置します。
  • hooks
    • hookを用意する場合は、ここに配置します。 ​​​​

ここでは、まずログインをするためのユーザー認証を行うコンポーネントの処理について説明します。

Login.tsxに処理を記述しています。

tsxという拡張子は、jsx形式のファイルで、typescriptで記述する際は、tsxにするのがお作法のようです。


このファイルの終わりの方にreturn文以降に以下のような記述があります。

これがjsx記法と呼ばれるもので、javascriptの中にHTML文を埋め込むことができます。

return (
<div>

<h1>ログイン</h1>

<form onSubmit={handleSubmit}>

<table>

<tbody>

<tr>

<td><label>利用者ID:</label></td>

<td><input name="userid" type="text" placeholder="userid" /></td>

</tr>

<tr>

<td><label>パスワード</label></td>

<td><input name="password" type="password" placeholder="password" /></td>

</tr>

<tr><td><button type="submit">ログイン</button></td></tr>

</tbody>

</table>

</form>

{isError && <p style={{ color: "red" }}>{`${errorText}`}</p>}

</div>

);

};

 

ここでユーザー名とパスワードを入力してもらって、ログイン認証を行う処理を作っていきます。

前回の記事でReactは基本SPAでサブミットはないという説明をしましたが、このログイン処理のように複数のデータをフォーム形式で入力してサブミットするというようなケースは多々あります。

方法は色々あるのですが、ここではreact-router-domというものを使っています。

それ以外にもnext.jsというフレームワークが有名です。

実際にサブミットしているように見えますが、実際にPOSTしているわけではなく、react-router-domというフレームワークの中であくまでもSPAの枠組みの中で処理は実装されている感じです。

(もしかしたらこの理解は間違っているかも)

ここでonSubmitコールバックとしてhandleSubmitというメソッドが呼ばれています。

ここの中括弧は、jsxの作法の1つで、そのカッコ内にjavascriptを記述できます。

ここでは、handleSubmitはJavaScriptの変数で、以下のように定義されています。

ちなみにReactでは変数定義は、基本const、たまにletが使われ、varは使いません。

古いJavaScriptしか知らない人にとってはここが1つのハードルかもしれません。

そして以下のコードは厳密に言うと、TypeScriptですが、あんまり褒められたTypeScriptコードではありません。

eventという変数の型としてanyを使っていますが、本当は適切なイベント型を指定するべきです。

そのイベントに紐づいた(ここはフォームデータなので)データ要素としてユーザー名とパスワードを取得しています。

そしてuserLoginCheckというメソッドを呼び出しています。

  const handleSubmit = (event: any) => {
    event.preventDefault();
    const { userid, password } = event.target.elements;
    userLoginCheck(userid.value, password.value).finally(() => 
    {
      if (login.status) {
        navigate("/Shop", { state: { customerId: login.customerId } })
      }
        
    })
  };

userLoginCheckの中身は以下のようになっています。

const userLoginCheck = async (userid: any, userpassword: any) => {
let status = false;

let customerId = 0;

setIsLoading(true);

setIsError(false);

await axios

.get<any>(`http://${serverAddress}:${serverPort}${applicationName}/checkpassword/${userid}/${userpassword}?IRISUsername=${username}&IRISPassword=${password}`)

.then((result: any) => {

if (result.data.authorized === 'ok') {

login.status = true;

login.customerId = result.data.ID;

}

else {

setIsError(true);

setErrorText('ログインが失敗しました');

}

})

.catch((error: any) => {

setIsError(true)

if (error.response) {

setErrorText(error.response.data.summary);

}

else if (error.request) {

setErrorText(error.request);

}

else {

setErrorText(error.message);

}

})

.finally(() => setIsLoading(false))

};



この後、RESTのインタフェースによりサーバーのAPIを呼び出します。

RESTのインタフェースの実装も複数ありますが、ネット上でサンプルがたくさん見つかるaxiosというライブラリを使用しています。

ここでREST APIのurlを指定します。 

このurlにcheckpasswordというメソッド名が含まれているのがわかると思います。

これがIRIS側で呼ばれるメソッド名となります。

ログインチェックがOKだったら、react-router-domに含まれるnavigateメソッドを呼び出して、/Shopにページ遷移します。

(実際にページ遷移しているわけではなくあくまでもエミュレーション)

あと、axiosの呼び出しは非同期なので、結果が返る前に呼び出しが戻ってくるので、最初はその動きになかなか慣れないかもしれません。

 

ログインが成功するとShop.tsxが呼ばれます。

このページはさらに複数のコンポーネントで構成されています。

そしてショッピングカートの機能を実装するためにcreateContextを使いコンテキスト情報を管理します。

navigate経由でページ遷移する際にデータを引き継ぐ仕組みとしてuseLocationというフックが用意されているので、それを使って情報を引き継ぎます。

jsx記法によるHTML定義の中で動的にデータを変更したい部分にはuseStateというフックを使用します。

import React from 'react';
import { createContext, useState, Dispatch,SetStateAction } from "react";
import { Header } from './Header';
import { ShoppingCart } from './ShoppingCart';
import { ProductList } from './ProductList';
import { useLocation } from "react-router-dom"
export type shopItem = {
        productCode: string;
        productName: string;
        price: number;
        units: number;
  };
  
export const ShopContext = createContext({} as {
    orderItems: shopItem[];
    setOrderItems: Dispatch<SetStateAction<shopItem[]>>;
    }
  ); 

export const Shop = () => {

  const [orderItems, setOrderItems] = useState<shopItem[]>([]);
  
  const location = useLocation();
        
  const values={orderItems,setOrderItems};
  
  return (
    <>
    <div className="title">
    <Header customerId = {location.state.customerId} /> 
    </div>
    <ShopContext.Provider value={values}>
    <div className="shoppingcart" style = {{ float: "left",width: "40%",height: "100%",overflow: "auto",border: "solid #000000 1px"}}>    
    <ShoppingCart customerId = {location.state.customerId} />
    </div>
    <div id="productlist" style = {{ width: "60%",height: "100%",overflow: "auto",border: "solid #000000 1px"}}>
    <ProductList />
    </div>
    </ShopContext.Provider>
    </>    
  );    
}
export default Shop;
Discussão (0)1
Entre ou crie uma conta para continuar