Meteor observer with two different applications - mongodb

Because changed data (inserted by application A) needs to be displayed in application B in realtime we decided to go with .find().observe(...).
It does look like:
App A -> Insert -> mongodb <- observe -> publish -> Display App B
This works fine but it has a delay of about 3-5 seconds between Inserting in A and displaying in B. How can i change this?
Initially i thought, Oplog-Observe-Driver is default in Meteor > Version 1 and does react in realtime. Does it still POLL or is there some other reason for the delay????
Thanks for your expanations.

If you're using Oplog, then the changes will be immediate. If you're using poll then it'll take a few seconds as you wrote.
You need to set MONGO_OPLOG_URL correctly to make this work. (And of course your MongoDB needs to be Oplog enabled.)
Also, you don't need to use find().observe() if you're in a reactive context, find() is enough. On the server though you might need find().observe() depending on what you're doing.

Did you use DDP.connect? You also have to use the onReconnect
Remote = DDP.connect('http://yourremoteserver');
MyCollection = new Mongo.Collection('same_name', Remote);
// do whatever you need with collection
let watchCollection = function (query={}, project={}) {
return MyCollection.find(query, project).observe({
changed: function () { console.log('Something changed!') }
});
}
DDP.onReconnect(watchCollection);

Related

ServerValue.increment doesn't work properly when Internet goes down

The addition of ServerValue.increment() (Add increment() for atomic field value increments #2437) was a great news as it allows field values ​​to be increased atomically in Firebase RTDB.
I have an application that keeps inventories and this function has been key because it allows updating the inventory regardless of whether the user is offline at times. However, I started to notice that sometimes the function is executed twice, which completely misstates the inventory in the wrong way.
To isolate the problem I decided to do the following test, which shows that ServerValue.Increment() works wrong when the connection goes from Online to Offline:
Make a for loop function from 1 to 200:
for (var i = 1; i <= 200; i++) {
testBloc.incrementTest(i);
print('Pos: $i');
}
The function incrementTest(i) must increment two variables: position (count from 1 in 1 up to 200) and sum (add 1 + 2 + 3, ..., + 200 which should result in 20,100)
Future<bool> incrementTest(int value) async {
try {
db.child('test/position')
.set(ServerValue.increment(1));
db.child('test/sum')
.set(ServerValue.increment(value));
} catch (e) {
print(e);
}
return true;
}
Note that db refers to the Firebase instance (FirebaseDatabase.instance.reference())
With this, comes the tests:
Test 1: 100% Online. PASSED
The function works properly, reaching the two variables to the correct result (in the Firebase console):
position: 200
sum: 20100
Test 2: 100% Offline. PASSED
To do this I used a physical device in airplane mode, then I executed the for loop function, and when the function finished executing I deactivated airplane mode and checked the result in the firebase console, which was satisfactory:
position: 200
sum: 20100
Test 3: Start Online and then go to Offline. FAILED
It is a typical operating scenario when the Internet Connection goes down. Even worse when the connections are intermittent, you are traveling on a subway or you are in a low coverage site for which Offline Persistence is a desired feature. To simulate it, what I did was run the for loop function in online mode, and before it finished, I put the physical device in airplane mode. Later I went Online to finish the test and see the results on the Firebase console. The results obtained are incorrect in all cases. Here are some of the results:
As you can see, the Increment was erroneously repeated 10, 18 and 9 times more.
How can I avoid this behavior?
Is there any other way to increment atomically a number in Firebase that works properly online / Offline ?
firebaser here
That's an interesting edge-case in the increment behavior. Between the client and the server neither can be certain whether the increment was executed or not, so it ends up being retried from the client upon the reconnect. This problem can only occur with the increment operation as far as I can tell, as all the other write operations are idempotent except for transactions, but those don't work while offline.
It is possible to ensure each increment happens only once, but it'll take some work:
First, add a nonce to write operation that unique identifies this operation. You can use a push key for this, but any other UUID also works fine. Combine this with your original set() call into a single multi-path update call, writing the nonce to a top-level node with a server-side timestamp as its value.
Now in your security rules for the top-level location, only allow the write if there is no existing data. This ensures the secondary writes you're seeing get rejected, and since security rules are checked across multi-path updates as a whole, the faulty increment will get rejected too.
You'll probably want to periodically clean up the node with nonce keys, based on the timestamp value in there. It won't matter for performance (since you're never searching here outside of during the cleanup), but may help control the storage cost for the nonces.
I haven't used this approach for this specific use-case yet, but have done it for others. If you'd include a client-side retry, the above essentially builds your own multi-path transaction mechanism, which is what I needed it for in the past. But since you don't need that here, it's simpler without that.
Based on #puf answer, you can proceed as follows:
Future<bool> incrementTest(int value, int dateOfToday) async {
var id = db.push().key;
Map<String, dynamic> _updates = {
'test/position': ServerValue.increment(1),
'test/sum': ServerValue.increment(value),
'test/nonce/$id': dateOfToday,
};
db.child('previousPath').update(_updates)
.catchError((error) => print('Increment Duplication Rejected ${error.message}'));
return true;
}
Then, in Firebase Security Rules, you need to add a rule in test/nonce/id location. Something as follows:
{
"previousPath": {
"test": {
".read": "auth != null", //It depends on your root rules
".write": "auth != null", //It depends on your root rules
"nonce": {
"$nonce_id": {
".validate": "!data.exists()" //THE MAGIC IS HERE
}
}
}
}
}
In this way, when the device tries to write to the database again (wrongly), Firebase will reject it since it already had a write with that same ID before.
I hope it serves someone else!!!

Avoiding repetitive calls when creating reactfire hooks

When initializing a component using reactfire, each time I add a reactfire hook (e.g. useFirestoreDocData), it triggers a re-render and therefore repeats all previous initialization. For example:
const MyComponent = props => {
console.log(1);
const firestore = useFirestore();
console.log(2);
const ref = firestore.doc('count/counter');
console.log(3);
const { value } = useFirestoreDocDataOnce(ref);
console.log(4);
...
return <span>{value}</span>;
};
will output:
1
1
2
3
1
2
3
4
This seems wasteful, is there a way to avoid this?
This is particularly problematic when I need the result of one reactfire hook to create another (e.g. retrieve data from one document to determine which other document to read) and it duplicates the server calls.
See React's documentation of Suspense.
Particulary that part: Approach 3: Render-as-You-Fetch (using Suspense)
Reactfire uses this mechanics. It is not supposed to fetch more than one time for each call even if the line is executed more than once. The mechanics behind "understand" that the fetch is already done and will start the next one.
In your case, react try to render your component, see it needs to fetch, stop rendering and show suspense's fallback while fetching. When fetch is done it retry to render your component and as the fetch is completed it will render completely.
You can confirm in your network tab that each calls is done only once.
I hope I'm clear, please don't hesitate to ask for more details if i'm not.

PWA creates IndexedDB database, but does not create any object stores

I have a Vue PWA and it stopped creating my IndexDB object stores on first load or upgrade. Here is my code, I am using the latest version of IDB (https://github.com/jakearchibald/idb):
await openDB('dbname', 1, {
upgrade(db, oldVersion, newVersion, transaction) {
switch (newVersion) {
case 0:
// a placeholder case so that the switch block will
// execute when the database is first created
// (oldVersion is 0)
// falls through
case 1:
db.createObjectStore('change_log', {keyPath: 'id'});
db.createObjectStore('person', {keyPath: 'id'})
.createIndex('username', 'username');
break;
}
}
});
I have tried multiple browsers and incognito tabs, etc. and the same thing always happens. The database is created, but no object stores are created. I use developer tools to clear all the data in the PWA and refresh but the same thing happens.
If I increment the version number, the version of my database gets updated in the browser, but the object stores still do not get added.
The upgrade() function does not get called.
I had this happen to me earlier in my development, and I fixed it, but I can't remember how. I feel like it may not actually be a coding issue...
OK, I found the problem. I added a logging mechanism to my App and there was code running BEFORE my upgrade code that was opening the database to create a log entry. Therefore, it was creating the database (with no object stores) before my upgrade method was being called. I changed my open database code to always include the upgrade method to solve my problems.

How to make a jenkins call to retrieve the job details every 'X' minutes in meteor?

What I'm up to is to get the jenkins job details and store it in mongo DB every "X" minutes. I have to make an HTTP.call(JenkinsURL) which I know how to do. My problem is calling it for specific intervals.
buildDetails=HTTP.call('GET',buildURL);
buildURL has the Jenkins job URL. I found this link which gives an overview of the code for my problem, but I don't know how and where i should place these code to get it working. I tried all possibility.
Is there any method in meteor which can make this possible to run a specific code to be run for every X min??
Is there any method in meteor which can make this possible to run a specific code to be run for every X min??
Yes, there is.
Meteor.setInterval that can be used to do something repetitively every X interval of time.
You can put your HTTP call within it on the server. Eg:
Meteor.startup({function(){
var timerID = Meteor.setInterval(function(){
buildDetails=HTTP.call('GET',buildURL);
// and other things
}, 60000) //60000ms = 1 min
}
});
When you want to stop the timer function, simply call Meteor.clearInterval
Meteor.clearInterval(timerID);

Not all changes in subscribed docs are loaded

I have such a method in my Meteor app:
addLocation: (location, tid) => {
location = LocationSchema.clean(location);
LocationSchema.validate(location);
var lid = Locations.insert(location);
Thoughts.update({_id: tid}, {$push: {locations: lid}});
}
I subscribe both Locations and Thoughts collections. But unfortunately after calling my method only changes in Locations collection are visible - my modified thought is still the same and I must reload page to see its changes. Is it bug in Meteor or my mistake? Do you know some ways to solve or bypass this problem?
What's more, when I push some value to locations array in some thought with Robomongo, changes are visible though. It looks like the problem is that two changes try to be seen at the same time.