2017-08-09 3 views
0

J'ai besoin de construire une API/serveur REST qui répond à plus de 15 000 requêtes HTTP par seconde en moins de 80 ms. Si nécessaire, je pourrais exécuter plusieurs instances avec un équilibreur de charge. Le serveur reçoit une requête avec une liste de critères (environ 20), ils doivent être analysés et comparés à un ensemble de règles (environ 2000 règles qui ont des valeurs différentes pour les 20 critères et une décision finale) qui décide de la réponse (Oui ou non).Comment concevoir une API HTTP python extrêmement rapide avec recherche de données (> 15K req/sec)?

Demande d'échantillon Charge utile:

{"Country" : "DE", 
"ID" : "998423-423432-4234234-234234", 
"Criteria1": "8748r78", 
"Criteria2": "Some String", 
    [...] 
} 

Sample ruleset (encore à déterminer, mais commençons par une conception simple):

+--------+---------+-------------+--------------+ 
| RuleId | Country | Criteria1 | Criteria2 | etc... 
+--------+---------+-------------+--------------+ 
|  1 | UK  | SomeString1 | SomeString3 | 
|  2 | UK  | SomeString1 | SomeString2 | 
|  3 | US  | SomeString4 | * (Wildcard) | 
+--------+---------+-------------+--------------+ 

Tous les critères peuvent contenir entre 1 et probablement autour de 400 différents valeurs, toutes les chaînes (par exemple GEO dans le code ISO). Certains peuvent être vides et être traités comme des caractères génériques. Théoriquement, il pourrait y avoir des entrées avec les 20 critères ayant la même valeur, mais c'est un sujet pour le moteur de règles à écrire.

J'ai fait quelques recherches comment y parvenir:

  1. En utilisant Sanic comme un serveur web pour un débit élevé, selon ma recherche c'est le plus rapide pour python qui est japronto excluant en alpha; Edit: Quelqu'un at-il de l'expérience avec les performances d'un serveur Web basé sur python + webframework concernant une utilisation similaire? Je lis seulement les benchmarks qui ont généralement un testcase très simple (il suffit de répondre une chaîne fixe à une requête, donc le nombre élevé de requêtes possibles par seconde dans tous les benchmarks)
  2. Utiliser sqlite3 (en mémoire) pour la recherche de règles; pas sûr si une déclaration SQL avec 20 contraintes est assez rapide? Peut-être il y a un autre moyen de comparer chaque demande à l'ensemble de règles sur 20 critères (chaque est une comparaison de chaînes). EDIT: Merci à un intervenant, je pourrais précalculer les règles en hachages et utiliser des hachages pour la recherche, ainsi une base de données pour la recherche en temps réel n'est pas nécessaire.
  3. Utilisez redis ou une autre base de données pour stocker les règles précalculées ( est un autre sujet) et préparez-les à être chargées dans chaque instance/worker du serveur http et donc dans la base de données sqlite3.
  4. Peut-être utiliser pypy3 pour plus speedup, mais je n'ai aucune expérience avec pypy

J'accueillerait cela sur Heroku. Donc, la question est: Quelles bibliothèques et donc l'architecture permettrait ce genre de vitesse avec Python?

+0

Pouvez-vous nous donner une exemple de demande et une règle d'exemple? Comment déterminez-vous quelle règle appliquer - correspondance la plus proche avec des critères donnés, par exemple la distance cartésienne? Comment "dense" est l'ensemble de règles, c'est-à-dire quelle est la plus grande distance attendue entre une requête et sa règle de correspondance la plus proche? À quelle fréquence le jeu de règles change-t-il? –

+1

2000 règles est * minuscule *. J'utiliserais une table de hachage en mémoire. –

+0

... notez également que Sanic est listé comme "pré-alpha" sur PyPi - je ne suis pas sûr que je veuille faire confiance à la production pour le moment. –

Répondre

1

Je suppose que

  1. tous les critères donnés sont chaînes exactes
  2. tous les critères non spécifiés rien correspondent (wildcard)
  3. nous pouvons rejeter toutes les règles qui produisent Faux
  4. règles peuvent contenir Aucun le résultat est True s'il y a au moins une règle qui correspond à tous les critères donnés, sinon False

Nous pouvons construire une consultation rapide comme dict (colonne) de dict (valeur) de jeu (IDs règle de correspondance):

from collections import namedtuple 

WILDCARD = None 

Rule = namedtuple("Rule", ["Country", "Criteria1", "Criteria2"]) 

rules = [ 
    Rule("UK", "Somestring1", "Somestring3"), 
    Rule("UK", "Somestring1", "Somestring2"), 
    Rule("US", "Somestring4", WILDCARD) 
] 

def build_lookup(rules): 
    columns = Rule._fields 
    # create lookup table (special handling of wildcard entries) 
    lookup = {column: {WILDCARD: set()} for column in columns} 
    # index rules by criteria 
    for id, rule in enumerate(rules): 
     for column, value in zip(columns, rule): 
      if value in lookup[column]: 
       lookup[column][value].add(id) 
      else: 
       lookup[column][value] = {id} 
    return lookup 

rule_lookup = build_lookup(rules) 

Avec les données d'échantillon données, rule_lookup contient maintenant

{ 
    'Country': {WILDCARD: set(), 'UK': {0, 1}, 'US': {2}}, 
    'Criteria1': {WILDCARD: set(), 'Somestring1': {0, 1}, 'Somestring4': {2}}, 
    'Criteria2': {WILDCARD: {2}, 'Somestring2': {1}, 'Somestring3': {0}} 
} 

alors nous pouvons répondre rapidement aux critères des règles comme

def all_matching_rules(criteria): 
    """ 
    criteria is a dict of {column: value} to match 

    Return a set of all rule ids which match criteria 
    """ 
    if criteria: 
     result = empty = set() 
     first = True 
     for column, value in criteria.items(): 
      ids = rule_lookup[column].get(value, empty) | rule_lookup[column][WILDCARD] 
      if first: 
       result = ids 
       first = False 
      else: 
       result &= ids # find intersection of sets 
      # short-circuit evaluation if result is null set 
      if not result: 
       break 
     return result 
    else: 
     # no criteria, return everything 
     return set(range(len(rules))) 

def any_rule_matches(criteria): 
    """ 
    criteria is a dict of {column: value} to match 

    Return True if any rule matches criteria, else False 
    """ 
    if criteria: 
     return bool(all_matching_rules(criteria)) 
    else: 
     return bool(len(rules)) 

qui fonctionne comme

>>> all_matching_rules({"Country": "UK", "Criteria2": "Somestring8"}) 
set() 

>>> all_matching_rules({"Country": "US", "Criteria2": "Somestring8"}) 
{2} 

>>> any_rule_matches({"Country": "UK", "Criteria2": "Somestring8"}) 
False 

>>> any_rule_matches({"Country": "US", "Criteria2": "Somestring8"}) 
True 

TimeIt rapporte que cela va à peu près 930ns sur ma machine - devrait être beaucoup assez vite ;-)