Distributed SQL query performance in Apache Ignite - gridgain

I have defined following 2 classes Person(with PersonKey) and Company() with companyId as key. PersonKey is affinity collocated with companyId. Now I am trying to do SQL distributed join (Person.companyId = Company.companyId) on 2 nodes connected in grid. I repeated same join with only single node. With distributed join in 2 nodes I should get 2x performance improvement, but it is performing worst with comparison to single node. Why is this happening? Are both nodes not participating in computation(here select query) part?
class PersonKey
{
// Person ID used to identify a person.
private int personId;
// Company ID which will be used for affinity.
#AffinityKeyMapped
private String companyId;
public PersonKey(int personId, String companyId)
{
this.personId = personId;
this.companyId = companyId;
}
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result
+ ((companyId == null) ? 0 : companyId.hashCode());
result = prime * result + personId;
return result;
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
PersonKey other = (PersonKey) obj;
if (companyId == null) {
if (other.companyId != null)
return false;
} else if (!companyId.equals(other.companyId))
return false;
if (personId != other.personId)
return false;
return true;
}
}
class Person
{
#QuerySqlField(index = true)
int personId;
#QuerySqlField(index = true)
String companyId;
public Person(int personId, String companyId)
{
this.personId = personId;
this.companyId = companyId;
}
private PersonKey key;
public PersonKey key()
{
if(key == null)
key = new PersonKey(personId, companyId);
return key;
}
}
class Company
{
#QuerySqlField(index = true)
String companyId;
String company_name;
public Company(String CompanyId, String company_name)
{
this.companyId = CompanyId;
this.company_name = company_name;
}
public String key()
{
return companyId;
}
}

Adding second node does not automatically mean that the query will become twice faster. Moreover, it can easily become slower because network is added, while in a single node deployment all the data is local.
To make the test more fair you can run a query from a client node [1] and change the number of server nodes. In this case the result set will be always sent across the network and you will see the real difference in performance with different number of servers.
[1] https://apacheignite.readme.io/docs/clients-vs-servers

Related

Acumatica Pagination Implementation for Processing Screen does not returns all applicable values

I have tried to implement pagination for my custom processing screen but not getting all the required values in the grid.
If I am using below code, where I did not implemented pagination and directly using BQL in the foreach() loop for getting the records, I am getting all the applicable values.
public PXCancel<KNPIPaymentFilter> cancel;
public PXFilter<KNPIPaymentFilter> KNPIFilter;
[PXFilterable]
public PXFilteredProcessing<SOOrder, KNPIPaymentFilter> KNPIProcessOrders;
protected bool _PayMethodChanged = false;
protected bool _PayActionChanged = false;
public virtual void KNPIPaymentFilter_RowUpdated(PXCache sender, PXRowUpdatedEventArgs e)
{
_PayMethodChanged = !sender.ObjectsEqual<KNPIPaymentFilter.paymentMethodID>(e.Row, e.OldRow);
_PayActionChanged = !sender.ObjectsEqual<KNPIPaymentFilter.payPalInvAction>(e.Row, e.OldRow);
}
public IEnumerable kNPIProcessOrders()
{
if (_PayMethodChanged || _PayActionChanged)
KNPIProcessOrders.Cache.Clear();
KNPIPaymentFilter filter = PXCache<KNPIPaymentFilter>.CreateCopy(KNPIFilter.Current);
if (filter.PayPalInvAction == KNPIConstants.SL)
yield break;
foreach (SOOrder order in PXSelect<SOOrder, Where<SOOrder.orderType, Equal<SOOrderTypeConstants.salesOrder>, And<SOOrder.paymentMethodID, Equal<Required<KNPIPaymentFilter.paymentMethodID>>,
And<SOOrder.status, NotEqual<SOOrderStatus.completed>>>>>.Select(this, this.KNPIFilter.Current.PaymentMethodID))
{
KNPIPayments PayPalPayment = new PXSelect<KNPIPayments, Where<KNPIPayments.acmOrderNbr, Equal<Required<KNPIPayments.acmOrderNbr>>,
And<KNPIPayments.acmOrderType, Equal<Required<KNPIPayments.acmOrderType>>>>, OrderBy<Desc<KNPIPayments.lineNbr>>>
(this).SelectWindowed(0, 1, order.OrderNbr, order.OrderType);
if (filter.PayPalInvAction == KNPIConstants.RE)
{
if (PayPalPayment == null || (PayPalPayment != null && PayPalPayment.PayPalInvoiceStatus == KNPIConstants.CANCELLED))
yield return order;
}
else if (filter.PayPalInvAction == KNPIConstants.CH)
{
if (PayPalPayment != null && PayPalPayment.PayPalInvoiceStatus == KNPIConstants.SENT)
yield return order;
}
}
KNPIProcessOrders.Cache.IsDirty = false;
}
But when I am implementing pagination logic like below, I am not getting any of the record in the grid. I have debugged the code and found out in the list object it is loading only 19 records initially but after that other records are not at all loading in the object.
public PXCancel<KNPIPaymentFilter> cancel;
public PXFilter<KNPIPaymentFilter> KNPIFilter;
[PXFilterable]
public PXFilteredProcessing<SOOrder, KNPIPaymentFilter> KNPIProcessOrders;
protected bool _PayMethodChanged = false;
protected bool _PayActionChanged = false;
public virtual void KNPIPaymentFilter_RowUpdated(PXCache sender, PXRowUpdatedEventArgs e)
{
_PayMethodChanged = !sender.ObjectsEqual<KNPIPaymentFilter.paymentMethodID>(e.Row, e.OldRow);
_PayActionChanged = !sender.ObjectsEqual<KNPIPaymentFilter.payPalInvAction>(e.Row, e.OldRow);
}
public IEnumerable kNPIProcessOrders()
{
if (_PayMethodChanged || _PayActionChanged)
KNPIProcessOrders.Cache.Clear();
KNPIPaymentFilter filter = PXCache<KNPIPaymentFilter>.CreateCopy(KNPIFilter.Current);
if (filter.PayPalInvAction == KNPIConstants.SL)
yield break;
PXSelectBase<SOOrder> cmd = new PXSelect<SOOrder, Where<SOOrder.orderType, Equal<SOOrderTypeConstants.salesOrder>, And<SOOrder.status, NotEqual<SOOrderStatus.completed>,
And<SOOrder.paymentMethodID, Equal<Optional<KNPIPaymentFilter.paymentMethodID>>>>>>(this);
int startRow = PXView.StartRow;
int totalRows = 0;
List<object> list = cmd.View.Select(new[] { KNPIFilter.Current }, null, PXView.Searches,
PXView.SortColumns, PXView.Descendings, PXView.Filters, ref startRow, PXView.MaximumRows, ref totalRows);
foreach (SOOrder order in list)
{
KNPIPayments PayPalPayment = new PXSelect<KNPIPayments, Where<KNPIPayments.acmOrderNbr, Equal<Required<KNPIPayments.acmOrderNbr>>,
And<KNPIPayments.acmOrderType, Equal<Required<KNPIPayments.acmOrderType>>>>, OrderBy<Desc<KNPIPayments.lineNbr>>>
(this).SelectWindowed(0, 1, order.OrderNbr, order.OrderType);
if (this.KNPIFilter.Current.PayPalInvAction == KNPIConstants.RE)
{
if (PayPalPayment == null || (PayPalPayment != null && PayPalPayment.PayPalInvoiceStatus == KNPIConstants.CANCELLED))
yield return order;
}
else if (this.KNPIFilter.Current.PayPalInvAction == KNPIConstants.CH)
{
if (PayPalPayment != null && PayPalPayment.PayPalInvoiceStatus == KNPIConstants.SENT)
yield return order;
}
}
PXView.StartRow = 0;
KNPIProcessOrders.Cache.IsDirty = false;
}
Can someone please help me out, as what I am doing wrong or what I am missing here.

Provide data lookup for PXSelector

I need advice either to convert the SQL to BQL or set the Resultset manipulated by C# code to the PXSelector.
I need to customize the AR Invoice and add 2 custom fields to record the COGS GL account and sub account for the inter company client when the inter company enter this invoice line as a bill. This custom field need to look up all sub accounts that is restricted to this Client's Branch ID and GL account. Basically all system's sub account lookup take care of the restriction group but for the custom fields; a custom PXSelector need to be written for this. Below is the SQL that supplies the require sub accounts but I need to know how to make the SQL query works in Acumatica
-- SQL for the required data
DECLARE #GLAccountCD nvarchar(10) = 'COGS'
DECLARE #BranchCD nvarchar(30) = 'PurchaseBranch'
SELECT *
FROM Sub
where (((CAST(Sub.groupmask as int) & CAST((SELECT GroupMask FROM Account WHERE AccountCD = #GLAccountCD AND CompanyID = 3 AND DeletedDatabaseRecord = 0) AS int)) > 1
AND (CAST(Sub.groupmask as int) & CAST((SELECT GroupMask FROM Branch WHERE BranchCD = #BranchCD AND CompanyID = 3 AND DeletedDatabaseRecord = 0) AS int)) > 1)
OR (Sub.GroupMask = 0 AND Sub.DeletedDatabaseRecord = 0))
AND CompanyID = 3
ORDER BY SubCD
--The below PXSelector provide all sub accounts regard of restriction group,
--I need the PXSelector to use the above SQL Query result
#region UsrAPBIllGLSubAccID
[PXDBInt]
[PXUIField(DisplayName="Bill COGS SubAccount")]
[PXSelector(typeof(Search<Sub.subID, Where<Sub.active, Equal<True>>, OrderBy<Desc<Sub.subCD>>>),
new Type[] {typeof(Sub.subCD),
typeof(Sub.description)},
SubstituteKey = typeof(Sub.subCD)
)]
public virtual int? UsrAPBIllGLSubAccID { get; set; }
public abstract class usrAPBIllGLSubAccID : IBqlField { }
#endregion
I think that would be achievable using the Match BQL clause.
GLAccess.cs file has BQL queries to restrict accounts based on Sub and Branch group mask using Match clause, this would be a good place to investigate:
public PXSelect<Sub> Sub;
protected virtual IEnumerable sub(
)
{
if (Group.Current != null && !String.IsNullOrEmpty(Group.Current.GroupName))
{
bool inserted = (Group.Cache.GetStatus(Group.Current) == PXEntryStatus.Inserted);
foreach (Sub item in PXSelect<Sub,
Where2<Match<Current<PX.SM.RelationGroup.groupName>>,
Or2<Match<Required<Sub.groupMask>>, Or<Sub.groupMask, IsNull>>>>
.Select(this, new byte[0]))
{
if (!inserted || item.Included == true)
{
Sub.Current = item;
yield return item;
}
else if (item.GroupMask != null)
{
PX.SM.RelationGroup group = Group.Current;
bool anyGroup = false;
for (int i = 0; i < item.GroupMask.Length && i < group.GroupMask.Length; i++)
{
if (group.GroupMask[i] != 0x00 && (item.GroupMask[i] & group.GroupMask[i]) == group.GroupMask[i])
{
Sub.Current = item;
yield return item;
}
anyGroup |= item.GroupMask[i] != 0x00;
}
if (!anyGroup)
{
Sub.Current = item;
yield return item;
}
}
}
}
else
{
yield break;
}
}
public PXSelect<Branch> Branch;
protected virtual IEnumerable branch(
)
{
if (Group.Current != null && !String.IsNullOrEmpty(Group.Current.GroupName))
{
bool inserted = (Group.Cache.GetStatus(Group.Current) == PXEntryStatus.Inserted);
foreach (Branch item in PXSelect<Branch,
Where2<Match<Current<PX.SM.RelationGroup.groupName>>,
Or<Match<Required<Branch.groupMask>>>>>
.Select(this, new byte[0]))
{
if (!inserted)
{
Branch.Current = item;
yield return item;
}
else if (item.GroupMask != null)
{
PX.SM.RelationGroup group = Group.Current;
bool anyGroup = false;
for (int i = 0; i < item.GroupMask.Length && i < group.GroupMask.Length; i++)
{
if (group.GroupMask[i] != 0x00 && (item.GroupMask[i] & group.GroupMask[i]) == group.GroupMask[i])
{
Branch.Current = item;
yield return item;
}
anyGroup |= item.GroupMask[i] != 0x00;
}
if (!anyGroup)
{
Branch.Current = item;
yield return item;
}
}
}
}
else
{
yield break;
}
}

I'm getting Resource Leaks

Whenever I run this code, it works pretty smoothly, until the while loop runs through once. It will go back and ask for the name again, and then skip String b = sc.nextLine();, and print the next line, instead.
static Scanner sc = new Scanner(System.in);
static public void main(String [] argv) {
Name();
}
static public void Name() {
boolean again = false;
do
{
System.out.println("What is your name?");
String b = sc.nextLine();
System.out.println("Ah, so your name is " + b +"?\n" +
"(y//n)");
int a = getYN();
System.out.println(a + "! Good.");
again = askQuestion();
} while(again);
}
static public boolean askQuestion() {
System.out.println("Do you want to try again?");
int answer = sc.nextInt();
if (answer == 1) {
return true;
}
else {
return false;
}
}
static int getYN() {
switch (sc.nextLine().substring(0, 1).toLowerCase()) {
case "y":
return 1;
case "n":
return 0;
default:
return 2;
}
}
}
Also, I'm trying to create this program in a way that I can ask three questions (like someone's Name, Gender, and Age, maybe more like race and whatnot), and then bring all of those answers back. Like, at the very end, say, "So, your name is + name +, you are + gender +, and you are + age + years old? Yes/No." Something along those lines. I know there's a way to do it, but I don't know how to save those responses anywhere, and I can't grab them since they only occur in the instance of the method.
Don't try to scan text with nextLine() AFTER using nextInt() with the same scanner! It may cause problems. Open a scanner method for ints only...it's recommended.
You could always parse the String answer of the scanner.
Also, using scanner this way is not a good practice, you could organize questions in array an choose a loop reading for a unique scanner instantiation like this:
public class a {
private static String InputName;
private static String Sex;
private static String Age;
private static String input;
static Scanner sc ;
static public void main(String [] argv) {
Name();
}
static public void Name() {
sc = new Scanner(System.in);
String[] questions = {"Name?","Age","Sex?"};//
int a = 0;
System.out.println(questions[a]);
while (sc.hasNext()) {
input = sc.next();
setVariable(a, input);
if(input.equalsIgnoreCase("no")){
sc.close();
break;
}
else if(a>questions.length -1)
{
a = 0;
}
else{
a++;
}
if(a>questions.length -1){
System.out.println("Fine " + InputName
+ " so you are " + Age + " years old and " + Sex + "." );
Age = null;
Sex = null;
InputName = null;
System.out.println("Loop again?");
}
if(!input.equalsIgnoreCase("no") && a<questions.length){
System.out.println(questions[a]);
}
}
}
static void setVariable(int a, String Field) {
switch (a) {
case 0:
InputName = Field;
return;
case 1:
Age = Field;
return;
case 2:
Sex = Field;
return;
}
}
}
Pay attention on the global variables, wich stores your info until you set them null or empty...you could use them to the final affirmation.
Hope this helps!
Hope this helps!

How to sort recordstore records based on a certain field in it?

For example there are three records in a recordstore , and the structure of a record in the recordstore is like this : lastname;firstname;moneyborrowed
I want to show these three records inside a LWUIT Table and I want them to be sorted by the lastname column. How to achieve that ?
save using
Preferences preferences = new Preferences("mydbname");
preferences.put("key","lastname;firstname;moneyborrowed");
preferences.save();
and retrieve using
String val = (string) preferences.get("key");
Preferences.java
import java.util.Enumeration;
import java.util.Hashtable;
import javax.microedition.rms.RecordEnumeration;
import javax.microedition.rms.RecordStore;
import javax.microedition.rms.RecordStoreException;
public class Preferences {
private final String mRecordStoreName;
private final Hashtable mHashtable;
public Preferences(String recordStoreName)
throws RecordStoreException {
mRecordStoreName = recordStoreName;
mHashtable = new Hashtable();
load();
}
public String get(String key) {
return (String)mHashtable.get(key);
}
public void put(String key, String value) {
if (value == null) value = "";
mHashtable.put(key, value);
}
private void load() throws RecordStoreException {
RecordStore rs = null;
RecordEnumeration re = null;
try {
rs = RecordStore.openRecordStore(mRecordStoreName, true);
re = rs.enumerateRecords(null, null, false);
while (re.hasNextElement()) {
byte[] raw = re.nextRecord();
String pref = new String(raw);
// Parse out the name.
int index = pref.indexOf('|');
String name = pref.substring(0, index);
String value = pref.substring(index + 1);
put(name, value);
}
}
finally {
if (re != null) re.destroy();
if (rs != null) rs.closeRecordStore();
}
}
public void save() throws RecordStoreException {
RecordStore rs = null;
RecordEnumeration re = null;
try {
rs = RecordStore.openRecordStore(mRecordStoreName, true);
re = rs.enumerateRecords(null, null, false);
// First remove all records, a little clumsy.
while (re.hasNextElement()) {
int id = re.nextRecordId();
rs.deleteRecord(id);
}
// Now save the preferences records.
Enumeration keys = mHashtable.keys();
while (keys.hasMoreElements()) {
String key = (String)keys.nextElement();
String value = get(key);
String pref = key + "|" + value;
byte[] raw = pref.getBytes();
rs.addRecord(raw, 0, raw.length);
}
}
finally {
if (re != null) re.destroy();
if (rs != null) rs.closeRecordStore();
}
}
}

Easy way to search a string for strings

I'm trying to find the easiest way to search a string for an array of possible strings. I know the easy way to do this for characters is to use myString.IndexOfAny(charArray). But how what if I'd like to search my string for strings and not just characters? Are there any .net tricks or methods that make this easier?
Basically, I'd like to do something like this:
string myName = "rahkim";
string[] names = new string[] {"joe","bob","chris"};
if(myName.IndexOfAny(names) >= 0)
{
//success code//
}
I know there are ways to do this with loops, etc. But I was hoping for something inherent in the framework.
You should define if you want to to find equal strings or search for a matching substring. Both ways are easy pre-LINQ and with LINQ.
string myName = "rahkim";
string[] names = new string[] { "joe", "bob", "chris" };
Equal Strings, LINQ
bool contains = names.Contains(myName);
Equal Strings, Pre-LINQ
bool contains = new List<string>(name).Contains(myName);
Substrings, LINQ
bool contains = names.Any(name => name.Contains(myName));
Substring, Pre-LINQ
bool contains = false;
foreach(string name in names)
if (name.Contains(myName))
contains = true;
If anyone else found this while trying to search for a .Net method like String.IndexOfAny(String[]), this is my solution:
C#
public int IndexOfAny(string test, string[] values)
{
int first = -1;
foreach (string item in values) {
int i = test.IndexOf(item);
if (i >= 0) {
if (first > 0) {
if (i < first) {
first = i;
}
} else {
first = i;
}
}
}
return first;
}
VB
Public Function IndexOfAny(test As String, values As String()) As Integer
Dim first As Integer = -1
For Each item As String In values
Dim i As Integer = test.IndexOf(item)
If i >= 0 Then
If first > 0 Then
If i < first Then
first = i
End If
Else
first = i
End If
End If
Next
Return first
End Function
You can do a LastIndexOfAny(String[]) by just switching the
i < first
to
i > first
You can (also) use the static IndexOf method of the Array class:
bool hasName = Array.IndexOf(names, myName) > -1;
int IndexOfAny(String[] rgs) would indeed be nice but it's nominally an O(n^2) operation. If, in your application, the set of strings rgs is large and always the same, the most efficient approach is to load them into a trie data structure once, and then use the trie repeatedly to search for them within the unknown strings given at runtime.
Here is the relevant code, adapted from a C# trie source I found on the web, attributed to "Kerry D. Wong." In my version, each string in the trie has a "payload" of generic type TValue. To use this trie to simply search for substrings, the payload could always be set to true, as illustrated with simple_trie.
The other thing I changed here is that this trie automatically adapts allow for storage of arbitrary Unicode strings. The array at each node—which characterizes a trie—adjusts its base and length to accomodate the range of Unicode characters which need to be stored at that node. This allows for case-sensitive matching, for example.
The C# 3.0 initialization syntax is handy for this trie, but enabling it requires a dummy implementation of IEnumerable in order to compile. The CLR doesn't seem to call GetEnumerator() and I suggest that you don't try to enumerate with its result either.
using System;
using System.Collections.Generic;
using System.Linq; // only used in Main()
class Program
{
// trie with payload of type <String>
static Trie<String> value_trie = new Trie<String>
{
{ "rabbit", "cute" },
{ "giraffe", "tall" },
{ "ape", "smart" },
{ "hippo", "large" },
};
// degenerate case of a trie without payload
static Trie<bool> simple_trie = new Trie<bool>
{
{ "rabbit", true },
{ "giraffe", true },
{ "ape", true },
{ "hippo", true },
};
static void Main(String[] args)
{
String s = "Once upon a time, a rabbit met an ape in the woods.";
// Retrieve payloads for words in the string.
//
// output:
// cute
// smart
foreach (String word in value_trie.AllSubstringValues(s))
Console.WriteLine(word);
// Simply test a string for any of the words in the trie.
// Note that the Any() operator ensures that the input is no longer
// traversed once a single result is found.
//
// output:
// True
Console.WriteLine(simple_trie.AllSubstringValues(s).Any(e=>e));
s = "Four score and seven years ago.";
// output:
// False
Console.WriteLine(simple_trie.AllSubstringValues(s).Any(e => e));
}
}
class TrieNode<TValue>
{
private TrieNode<TValue>[] nodes = null;
private TValue m_value = default(TValue);
private Char m_base;
public Char Base { get { return m_base; } }
public bool IsEnd { get { return !m_value.Equals(default(TValue)); } }
public TValue Value
{
get { return m_value; }
set { m_value = value; }
}
public IEnumerable<TrieNode<TValue>> Nodes { get { return nodes; } }
public TrieNode<TValue> this[char c]
{
get
{
if (nodes != null && m_base <= c && c < m_base + nodes.Length)
return nodes[c - m_base];
return null;
}
}
public TrieNode<TValue> AddChild(char c)
{
if (nodes == null)
{
m_base = c;
nodes = new TrieNode<TValue>[1];
}
else if (c >= m_base + nodes.Length)
{
Array.Resize(ref nodes, c - m_base + 1);
}
else if (c < m_base)
{
Char c_new = (Char)(m_base - c);
TrieNode<TValue>[] tmp = new TrieNode<TValue>[nodes.Length + c_new];
nodes.CopyTo(tmp, c_new);
m_base = c;
nodes = tmp;
}
TrieNode<TValue> node = nodes[c - m_base];
if (node == null)
{
node = new TrieNode<TValue>();
nodes[c - m_base] = node;
}
return node;
}
};
class Trie<TValue> : System.Collections.IEnumerable
{
private TrieNode<TValue> _root = new TrieNode<TValue>();
// This dummy enables C# 3.0 initialization syntax
public System.Collections.IEnumerator GetEnumerator()
{
return null;
}
public void Add(String s, TValue v)
{
TrieNode<TValue> node = _root;
foreach (Char c in s)
node = node.AddChild(c);
node.Value = v;
}
public bool Contains(String s)
{
TrieNode<TValue> node = _root;
foreach (Char c in s)
{
node = node[c];
if (node == null)
return false;
}
return node.IsEnd;
}
public TValue Find(String s_in)
{
TrieNode<TValue> node = _root;
foreach (Char c in s_in)
{
node = node[c];
if (node == null)
return default(TValue);
}
return node.Value;
}
public IEnumerable<TValue> FindAll(String s_in)
{
TrieNode<TValue> node = _root;
foreach (Char c in s_in)
{
node = node[c];
if (node == null)
break;
if (node.Value != null)
yield return node.Value;
}
}
public IEnumerable<TValue> AllSubstringValues(String s)
{
int i_cur = 0;
while (i_cur < s.Length)
{
TrieNode<TValue> node = _root;
int i = i_cur;
while (i < s.Length)
{
node = node[s[i]];
if (node == null)
break;
if (node.Value != null)
yield return node.Value;
i++;
}
i_cur++;
}
}
};
Here's the right syntax:
if(names.Contains(myName))
{
//success code//
}
if (names.Contains(myName))
{
//success code//
}

Resources