What could be better than the description of the nine great chips App Engine? Of course, a description of ten. While participating in
group discussions , we noticed that some of the App Engine features often go unnoticed, so we chose a little less than eleven interesting facts that may simply allow you to write programs differently. But enough words, look at the first chip:
1. Application Versions are Strings, Not Numbers
Although most examples have a number in the 'version' field in app.yaml and in appengine-web.xml, however, this is just a matter of agreement. The application version can be any string that can be used in URLs. For example, you can name the versions "live" and "dev", and they will be available at the addresses "live.latest.
yourapp .appspot.com "and" dev.latest.
yourapp .appspot.com.
2. You may have several simultaneous versions of the application.
As we hinted in paragraph 1, App Engine allows you to deploy several versions of the application and work with them in parallel. All versions use the same data store and memcache, but they are executed in different instances and they have different URLs. The 'live' version serves the address of yourapp.appspot.com (and all other domains that you have connected), and all other versions of the applications are available at the addresses of the form version.latest.yourapp.appspot.com. It is convenient to use multiple versions for testing a new release in a working environment, on real data, before you open it for everyone.
It is less known that different versions of the application do not even need to use the same language! When you have one version of the application written in Java, and the other on Python, this is pretty funny.
')
3. The Java runtime environment supports any languages ​​that are compiled into Java bytecode.
The runtime is called java, but in fact nothing prevents you from writing an AppEngine application in any language that is compiled into JVM bytecode. In general, there are already people who write AppEngine applications in JRuby, Groovy, Scala, Rhino (JavaScript interpreter), Quercus (PHP interpreter / compiler), and even in Jython! On this
wiki page, our community shares the knowledge of what has earned and what has not.
4. The 'IN' and '! =' Operators generate several queries to the data warehouse.
Operators 'IN' and '! =' In the Python execution environment are actually implemented in the SDK and 'under the hood' are translated into several requests.
For example, the query "SELECT * FROM People WHERE name IN ('Bob', 'Jane')" is translated into two queries equivalent to the execution of "SELECT * FROM People WHERE name = 'Bob'" and "SELECT * FROM People WHERE name = ' Jane 'and merge results. Using several operators at the same time increases the number of necessary queries even more, thus the query “SELECT * FROM People WHERE name IN ('Bob', 'Jane') AND age! = 25 'fulfills four real queries in total, for all variants of conditions (age less than or more than 25 years, and the name 'Bob' or 'Jane'), and then combines them into one result.
As a result, it is better to try to avoid using several such operators in a single query. For example, if you use a query with inequality, and expect that only a small number of entries will meet the criteria (say, in the example above, you know that very few people have an age of exactly 25 years), then it may be more efficient to execute the query without inequality and himself to go through the results and discard those records that are not suitable.
5. For greater efficiency, you can combine the insert, query and delete operations into packages.
Every time you make a request to a data store, such as a sample or a get () operation, your application sends this request to the store, the request is processed there, and a response is sent back. This request-response cycle takes time, and if you do a lot of operations one by one, then by the time that the user waits for the result, a significant addition can be added.
Fortunately, there is a simple way to reduce the number of windings to and fro: batch operations. Each of the db.put (), db.get () and db.delete () functions, in addition to its normal single call, can accept a list as input. If you give them a list, they will perform operations with all the elements in the list in one call to the data store, and in parallel, saving you a lot of time. For example, look at this typical solution:
for entity in MyModel.all().filter("color =", old_favorite).fetch(100): entity.color = new_favorite entity.put()
Updating in this style requires accessing the repository to fetch data, plus one additional request for each update of the object - resulting in 101 calls to the repository! For comparison, look at this example:
updated = [] for entity in MyModel.all().filter("color =", old_favorite).fetch(100): entity.color = new_favorite updated.append(entity) db.put(updated)
By adding two lines, we reduced the number of calls to the repository from 101 to 2!
6. The performance of the data warehouse does not depend on how many objects are stored in it.
Many people ask how the store will behave, if you save 100,000, or a million, or ten million objects. One of the main advantages of storage is that its performance is completely independent of the number of objects stored by your application. Moreover, virtually all objects of all applications in App Engine are stored in one BigTable table! Further, with regard to requests, all requests that can be executed “natively” (excluding those that use the operators 'IN' and '! =' - see above) have the same execution cost: the cost of execution of the request is proportional to the number of records which he returns.
7. The time required to create an index does not fully depend on its size.
When adding a new index to an application on App Engine, it sometimes happens that it is created for quite a long time. Asking about the reasons for this, many mention the amount of data, comparing it with the time spent. However, the fact is that requests to create new indexes are organized into a queue and processed by a centralized system that creates indexes for all AppEngine applications. At peak times, your request to create an index may be different, and we can start creating your index a little later.
8. The amount of stored data ('Stored Data') is calculated once a day.
Once a day we run the task of recalculating the value of 'Stored Data' for your application. It uses the value of the actual use of the data warehouse at this point in time. In between, we update the image, approximately taking into account how you use the storage, so you can see all the changes in usage rather quickly. This shows why many people observed such an effect when, after removing a large number of objects, their storage still remained “full” for some time. When paying, of course, only confirmed values ​​are used.
9. The order in which the handlers are listed in the app.yaml, web.xml, and appengine-web.xml files matter
Very often, when configuring an application, they forget that handlers in the configuration files are processed in order, from top to bottom, and this causes complex errors. For example, when setting up remote_api, many do this:
handlers: - url: /.* script: request.py - url: /remote_api script: $PYTHON_LIB/google/appengine/ext/remote_api/handler.py login: admin
At first glance, everything is in order here, but since handlers are processed in order, the request.py handler is encountered first and all requests — even to remote_api — go through request.py. Since request.py does not know anything about remote_api, it returns a 404 Not Found error. The solution is simple - make sure that the “catch all” handler stands at the very end.
The same applies to the Java runtime, but with one more limitation: all static handlers from appengine-web.xml are processed earlier than any dynamic handlers from web.xml.
10. No need to write GQL queries with hands
There is one very frequent anti-pattern that looks like this:
q = db.GqlQuery("SELECT * FROM People " "WHERE first_name = '" + first_name + "' AND last_name = '" + last_name + "'")
In addition to making your code vulnerable to injections, you will have to deal with escaping characters (and what if a user has an apostrophe in the name?) And, potentially, with encodings. Fortunately, GqlQuery can do parameter substitution, the standard way to avoid explicitly adding lines. With parameter substitution, the above query can be rewritten as:
q = db.GqlQuery("SELECT * FROM People " "WHERE first_name = :1 " "AND last_name = :2", first_name, last_name)
In addition to the numbered parameters, GqlQuery also supports named and name-value pairs as an argument:
q = db.GqlQuery("SELECT * FROM People " "WHERE first_name = :first_name " "AND last_name = :last_name", first_name=first_name, last_name=last_name)
This not only produces a cleaner code, but you can also use some interesting optimizations. If you need to execute the same query many times with different values, you can use GqlQuery-in-language .bind () to 'connect' parameter values ​​for each query. This is faster than creating a new request every time, because you only have to parse the request once:
q = db.GqlQuery("SELECT * FROM People " "WHERE first_name = :first_name " "AND last_name = :last_name") for first, last in people: q.bind(first, last) person = q.get() print person
By Nick Johnson, App Engine Team
Java is a trademark or registered trademark of Sun Microsystems, Inc. in the United States and other countries.Ps. Strangely, I only complained about “drafts” and “previews”, I did not press “publish” with a guarantee - and the topic was published anyway (naturally, unfinished). I apologize to all those who are embarrassed - and do not be so nervous :-)