I try to use Meteor.publish (server side) with this query:
return Meteor.users.find({_id:{$ne:this.userId}});
When I do a query on the client side with Meteor.subscribe, it works:
return Meteor.users.find({_id:{$ne:Meteor.userId()}});
so why is it not working on the server side...It seems I can only query it once its on the client side...The problem is, I do not want to download the entire collection because I will have over 20,000 users. Do publish methods not allow '$' queries?
Also how can I attach this to my following query statement:
return Meteor.users.find({"profile.loc":{ $near: [ to[0].profile.loc.lat, to[0].profile.loc.lon ], $maxDistance: (1/111.2)*250}});
As the answer above states, you would anyway publish 19,999 users if you do what you wrote.
Your question is in 2 parts though, and you should really address both with one query: to do this you should have a publication with parameter:
Meteor.publish('users', function(location) {
return Meteor.users.find(
{_id: {$ne: this.userId},
"profile.loc":{
$near: [ location.lat, location.lon ],
$maxDistance: (1/111.2)*250}
}
});
That will screen users on the server who match the location criteria.
On the client side you subscribe to it with:
location = {lon: 12.123, lat: 110.2};
Meteor.subscribe('users', location);
or the object of your choice.
The publish method does allow $ queries. What your publication is doing is publishing all users whose id is not equal to this.userId. If you have 20,000 users, this method will publish data for 19,999 users.
If you only want to publish the current user's data then try:
Meteor.publish('userData', function() { return Meteor.users.find(this.userId) };
Related
I am using the same query (or slightly modified but using the same MongoDB Collection) in various functions and on Mongo Atlas, we get only the collection name and query but don't know which function actually triggered it.
Is there any way to tag the queries with the functions so it can be easy to debug?
Two things come to mind.
The first is the ability to apply a comment to a query:
test> db.foo.find({x:123}).comment("unique identifier xyz");
test> db.system.profile.find().sort({ts:-1}).limit(1)
[
{
op: 'query',
ns: 'test.foo',
command: {
find: 'foo',
filter: { x: 123 },
comment: 'unique identifier xyz',
lsid: { id: UUID("6ce75fd2-dd4a-4c4e-9655-b80eca8ef755") },
'$db': 'test'
},
keysExamined: 0,
docsExamined: 10,
...
There is also the ability to apply an appName in the connection string.
Above I demonstrated the database profiler is one place where comments can show up, if configured. The full list of availability is described in the documentation.
For comments:
comment() associates a comment string with the find operation. This can make it easier to track a particular query in the following diagnostic outputs:
The system.profile
The QUERY log component
db.currentOp()
See configure log verbosity for the mongod log, the Database Profiler tutorial, or the db.currentOp() command.
And for appName:
Specify a custom app name. The app name appears in:
mongod and mongos logs
the currentOp.appName field in the currentOp command and db.currentOp() method output
the system.profile.appName field in the database profiler output
The appName connection option is available for:
MongoDB Drivers starting in MongoDB 4.0
mongosh starting in mongosh 1.1.9
MongoDB Compass starting in Compass 1.28.4
our current setup is: SPA frontend, Azure functions with mongoose middleware, MongoDB
(Maybe first read the question***)
Since we have a lot of documents in our DB and our customer wants to query them we are facing the following problem:
The user is assigned to his organization. He wants to search for Doc1s he has not responded to.
Doc1
{
_id
organization -> partitionKey
content
}
By creating doc2 with reference to doc1 he can respond.
Doc2
{
_id
organization -> partitionKey
Doc1ref
content
}
We have a 1:n relationship.
At the moment we filter just by query criteria of doc1 with limit and skip options.
But the new requirement is to filter the same way by referring doc2s.
I was thinking of:
Doing it in my code => Problem: after we have read with limit=100 and I filter it by my code, the result is not 100 anymore.
Extending doc1 by doc2 arrays => Must be the last option
Dynamic aggregation, Prepared in the code and executed at runtime => Don't want to user dynamic aggregations and the benefits of mongoose are almost lost.
Create a MongoDB view with lookup aggregation (populating doc1 by doc1.respondedOrganizations) => Problem is see here is the performance. When searching a lot of documents and then joining them by a non partitionKey.
*** So, I come to my question:
Is it possible to pass a virtual (not existing) query criteria...
doc1.find({ alreadyResponded : my.organization } )
...and use it as input variable in an aggregation
{
$lookup: {
from: Doc2s,
localField: _id,
foreignField: Doc1ref,
as: < output array field >
pipeline: [{
$match: {
$organization: {
$eq: $$alreadyResponded
}]
}
}
It would reduce query performance extremly.
Thanks
I'm currently testing the paylike's web sdk and I can use the sandbox easy. But how can I avoid the user can change the amount on the client side? The amount parameter is required, but how can I ensure about after a success callback about the amount? Can I get it from the server side?
The following code is fine, but I have problem with the amount parameter
<script src="//sdk.paylike.io/3.js"></script>
<script>
var paylike = Paylike('your key');
paylike.popup({
currency: 'DKK',
amount: 1000,
}, function( err, res ){
if (err)
return console.log(err);
console.log(res.transaction.id);
alert('Thank you!');
});
</script>
Two steps are important regarding transactions. The first step is authorization.
Authorization is done with the code you added here, on the frontend. The user can tamper with the amount, but this is merely a reservation and is not taking funds from the payer credit card.
The second step is called capture. You can only capture the funds from the Paylike dashboard, or via your server. When you do that, you generally send the same amount that you initially wanted the user to pay, and if the authorization were less, you would get an error. You can also fetch the transaction to inspect the amount that was authorized if you want to reject an order, for example. You can also send a custom parameter that you might use to validate on the server, similar to a checksum if you want to.
You have a private key, which users are not able to get, so that makes it safe. The 2 step approach is a validation on its own, but as I mentioned, you can also inspect the transaction.
You can check the API docs here: https://github.com/paylike/api-docs, where you will also find links to client-side SDKs.
If you are using PHP, using the PHP library (which I maintain) you can do this to inspect a transaction:
$paylike = new \Paylike\Paylike($private_api_key);
$transactions = $paylike->transactions();
$transaction = $transactions->fetch($transaction_id);
The transaction variable will look like this:
{
"id":"5da8272132aad2256xxxxxxx",
"test":true,
"merchantId":"594d3c455be12d547xxxxxx",
"created":"2019-10-17T08:32:34.362Z",
"amount":35,
"refundedAmount":0,
"capturedAmount":0,
"voidedAmount":0,
"pendingAmount":35,
"disputedAmount":0,
"card":{
"id":"5da82743735e61604xxxxxxx",
"bin":"410000",
"last4":"0000",
"expiry":"2023-11-30T22:59:59.999Z",
"code":{
"present":true
},
"scheme":"visa"
},
"tds":"none",
"currency":"JPY",
"custom":{
"email":"customer#example.com",
"orderId":"Could not be determined at this point",
"products":[
[
{
"ID":"48",
"name":"Hoodie with Pocket",
"quantity":"1"
}
]
],
"customer":{
"name":"John Doe",
"email":"customer#example.com",
"phoneNo":"020 91X XXXX",
"address":"123 Main Street, New York, NY 10030",
"IP":"10.0.2.2"
},
"platform":{
"name":"WordPress",
"version":"5.2.4"
},
"ecommerce":{
"name":"WooCommerce",
"version":"3.7.1"
},
"paylikePluginVersion":"1.7.2"
},
"recurring":false,
"successful":true,
"error":false,
"descriptor":"PHP API WRAPPER TEST",
"trail":[
]
}
I am very new in GraphQL and trying to do a simple join query. My sample tables look like below:
{
phones: [
{
id: 1,
brand: 'b1',
model: 'Galaxy S9 Plus',
price: 1000,
},
{
id: 2,
brand: 'b2',
model: 'OnePlus 6',
price: 900,
},
],
brands: [
{
id: 'b1',
name: 'Samsung'
},
{
id: 'b2',
name: 'OnePlus'
}
]
}
I would like to have a query to return a phone object with its brand name in it instead of the brand code.
E.g. If queried for the phone with id = 2, it should return:
{id: 2, brand: 'OnePlus', model: 'OnePlus 6', price: 900}
TL;DR
Yes, GraphQL does support a sort of pseudo-join. You can see the books and authors example below running in my demo project.
Example
Consider a simple database design for storing info about books:
create table Book ( id string, name string, pageCount string, authorId string );
create table Author ( id string, firstName string, lastName string );
Because we know that Author can write many Books that database model puts them in separate tables. Here is the GraphQL schema:
type Query {
bookById(id: ID): Book
}
type Book {
id: ID
title: String
pageCount: Int
author: Author
}
type Author {
id: ID
firstName: String
lastName: String
}
Notice there is no authorId on the Book type but a type Author. The database authorId column on the book table is not exposed to the outside world. It is an internal detail.
We can pull back a book and it's author using this GraphQL query:
{
bookById(id:"book-1"){
id
title
pageCount
author {
firstName
lastName
}
}
}
Here is a screenshot of it in action using my demo project:
The result nests the Author details:
{
"data": {
"book1": {
"id": "book-1",
"title": "Harry Potter and the Philosopher's Stone",
"pageCount": 223,
"author": {
"firstName": "Joanne",
"lastName": "Rowling"
}
}
}
}
The single GQL query resulted in two separate fetch-by-id calls into the database. When a single logical query turns into multiple physical queries we can quickly run into the infamous N+1 problem.
The N+1 Problem
In our case above a book can only have one author. If we only query one book by ID we only get a "read amplification" against our database of 2x. Imaging if you can query books with a title that starts with a prefix:
type Query {
booksByTitleStartsWith(titlePrefix: String): [Book]
}
Then we call it asking it to fetch the books with a title starting with "Harry":
{
booksByTitleStartsWith(titlePrefix:"Harry"){
id
title
pageCount
author {
firstName
lastName
}
}
}
In this GQL query we will fetch the books by a database query of title like 'Harry%' to get many books including the authorId of each book. It will then make an individual fetch by ID for every author of every book. This is a total of N+1 queries where the 1 query pulls back N records and we then make N separate fetches to build up the full picture.
The easy fix for that example is to not expose a field author on Book and force the person using your API to fetch all the authors in a separate query authorsByIds so we give them two queries:
type Query {
booksByTitleStartsWith(titlePrefix: String): [Book] /* <- single database call */
authorsByIds(authorIds: [ID]) [Author] /* <- single database call */
}
type Book {
id: ID
title: String
pageCount: Int
}
type Author {
id: ID
firstName: String
lastName: String
}
The key thing to note about that last example is that there is no way in that model to walk from one entity type to another. If the person using your API wants to load the books authors the same time they simple call both queries in single post:
query {
booksByTitleStartsWith(titlePrefix: "Harry") {
id
title
}
authorsByIds(authorIds: ["author-1","author-2","author-3") {
id
firstName
lastName
}
}
Here the person writing the query (perhaps using JavaScript in a web browser) sends a single GraphQL post to the server asking for both booksByTitleStartsWith and authorsByIds to be passed back at once. The server can now make two efficient database calls.
This approach shows that there is "no magic bullet" for how to map the "logical model" to the "physical model" when it comes to performance. This is known as the Object–relational impedance mismatch problem. More on that below.
Is Fetch-By-ID So Bad?
Note that the default behaviour of GraphQL is still very helpful. You can map GraphQL onto anything. You can map it onto internal REST APIs. You can map some types into a relational database and other types into a NoSQL database. These can be in the same schema and the same GraphQL end-point. There is no reason why you cannot have Author stored in Postgres and Book stored in MongoDB. This is because GraphQL doesn't by default "join in the datastore" it will fetch each type independently and build the response in memory to send back to the client. It may be the case that you can use a model that only joins to a small dataset that gets very good cache hits. You can then add caching into your system and not have a problem and benefit from all the advantages of GraphQL.
What About ORM?
There is a project called Join Monster which does look at your database schema, looks at the runtime GraphQL query, and tries to generate efficient database joins on-the-fly. That is a form of Object Relational Mapping which sometimes gets a lot of "OrmHate". This is mainly due to Object–relational impedance mismatch problem.
In my experience, any ORM works if you write the database model to exactly support your object API. In my experience, any ORM tends to fail when you have an existing database model that you try to map with an ORM framework.
IMHO, if the data model is optimised without thinking about ORM or queries then avoid ORM. For example, if the data model is optimised to conserve space in classical third normal form. My recommendation there is to avoid querying the main data model and use the CQRS pattern. See below for an example.
What Is Practical?
If you do want to use pseudo-joins in GraphQL but you hit an N+1 problem you can write code to map specific "field fetches" onto hand-written database queries. Carefully performance test using realist data whenever any fields return an array.
Even when you can put in hand written queries you may hit scenarios where those joins don't run fast enough. In which case consider the CQRS pattern and denormalise some of the data model to allow for fast lookups.
Update: GraphQL Java "Look-Ahead"
In our case we use graphql-java and use pure configuration files to map DataFetchers to database queries. There is a some generic logic that looks at the graph query being run and calls parameterized sql queries that are in a custom configuration file. We saw this article Building efficient data fetchers by looking ahead which explains that you can inspect at runtime the what the person who wrote the query selected to be returned. We can use that to "look-ahead" at what other entities we would be asked to fetch to satisfy the entire query. At which point we can join the data in the database and pull it all back efficiently in the a single database call. The graphql-java engine will still make N in-memory fetches to our code. The N requests to get the author of each book are satisfied by simply lookups in a hashmap that we loaded out of the single database call that joined the author table to the books table returning N complete rows efficiently.
Our approach might sound a little like ORM yet we did not make any attempt to make it intelligent. The developer creating the API and our custom configuration files has to decide which graphql queries will be mapped to what database queries. Our generic logic just "looks-ahead" at what the runtime graphql query actually selects in total to understand all the database columns that it needs to load out of each row returned by the SQL to build the hashmap. Our approach can only handle parent-child-grandchild style trees of data. Yet this is a very common use case for us. The developer making the API still needs to keep a careful eye on performance. They need to adapt both the API and the custom mapping files to avoid poor performance.
GraphQL as a query language on the front-end does not support 'joins' in the classic SQL sense.
Rather, it allows you to pick and choose which fields in a particular model you want to fetch for your component.
To query all phones in your dataset, your query would look like this:
query myComponentQuery {
phone {
id
brand
model
price
}
}
The GraphQL server that your front-end is querying would then have individual field resolvers - telling GraphQL where to fetch id, brand, model etc.
The server-side resolver would look something like this:
Phone: {
id(root, args, context) {
pg.query('Select * from Phones where name = ?', ['blah']).then(d => {/*doStuff*/})
//OR
fetch(context.upstream_url + '/thing/' + args.id).then(d => {/*doStuff*/})
return {/*the result of either of those calls here*/}
},
price(root, args, context) {
return 9001
},
},
In the MEAN app I'm currently building, the client-side makes a $http POST request to my API with a JSON array of soundcloud track data specific to that user. What I now want to achieve is for those tracks to be saved to my app database under a 'tracks' table. That way I'm then able to load tracks for that user from the database and also have the ability to create unique client URLs (/tracks/:track)
Some example data:
{
artist: "Nicole Moudaber"
artwork: "https://i1.sndcdn.com/artworks-000087731284-gevxfm-large.jpg?e76cf77"
source: "soundcloud"
stream: "https://api.soundcloud.com/tracks/162626499/stream.mp3?client_id=7d7e31b7e9ae5dc73586fcd143574550"
title: "In The MOOD - Episode 14"
}
This data is then passed to the API like so:
app.post('/tracks/add/new', function (req, res) {
var newTrack;
for (var i = 0; i < req.body.length; i++) {
newTrack = new tracksTable({
for_user: req.user._id,
title: req.body[i].title,
artist: req.body[i].artist,
artwork: req.body[i].artwork,
source: req.body[i].source,
stream: req.body[i].stream
});
tracksTable.find({'for_user': req.user._id, stream: req.body[i].stream}, function (err, trackTableData) {
if (err)
console.log('MongoDB Error: ' + err);
// stuck here - read below
});
}
});
The point at which I'm stuck, as marked above is this: I need to check if that track already exists in the database for that user, if it doesn't then save it. Then, once the loop has finished and all tracks have either been saved or ignored, a 200 response needs to be sent back to my client.
I've tried several methods so far and nothing seems to work, I've really hit a wall and so help/advice on this would be greatly appreciated.
Create a compound index and make it unique.
Using the index mentioned above will ensure that there are no documents which have the same for_user and stream.
trackSchema.ensureIndex( {for_user:1, stream:1}, {unique, true} )
Now use the mongoDB batch operation to insert multiple documents.
//docs is the array of tracks you are going to insert.
trackTable.collection.insert(docs, options, function(err,savedDocs){
//savedDocs is the array of docs saved.
//By checking savedDocs you can see how many tracks were actually inserted
})
Make sure to validate your objects as by using .collection we are bypassing mongoose.
Make a unique _id based on user and track. In mongo you can pass in the _id that you want to use.
Example {_id : "NicoleMoudaber InTheMOODEpisode14",
artist: "Nicole Moudaber"
artwork: "https://i1.sndcdn.com/artworks-000087731284-gevxfm-large.jpg?e76cf77"
source: "soundcloud"
stream: "https://api.soundcloud.com/tracks/162626499/stream.mp3? client_id=7d7e31b7e9ae5dc73586fcd143574550"
title: "In The MOOD - Episode 14"}
_id must be unique and won't let you insert another document with the same _id. You could also use this to find the record later db.collection.find({_id : NicoleMoudaber InTheMOODEpisode14})
or you could find all tracks for db.collection.find({_id : /^NicoleMoudaber/}) and it will still use the index.
There is another method to this that I can explain if you dont' like this one.
Both options will work in a sharded environment as well as a single replica set. "Unique" indexes do not work in a sharded environment.
Soundcloud API provides a track id, just use it.
then before inserting datas you make a
tracks.find({id_soundcloud : 25645456}).exec(function(err,track){
if(track.length){ console.log("do nothing")}else {//insert}
});